Servers Get Popped — illustration with an open padlock, warning triangle and exposed port

Common Misconfigurations That Get Servers Popped

Most servers don’t get compromised through a novel exploit. They get compromised through something boring that was already documented, already fixable, and just never got done — a default password nobody rotated, a port that was supposed to be internal-only, a config file that leaks more than it should. This is the checklist I actually run through, in the order I run through it.

1. Default and reused credentials

Database admin panels, router web UIs, monitoring dashboards, CI tools — plenty still ship with a default username and password, and plenty of teams never change it because the service “isn’t really exposed.” Check every admin interface you run against its documented default credentials explicitly; don’t assume someone already did it. Where you can, enforce unique credentials per service rather than one shared password across your stack — one leaked credential shouldn’t be a master key.

2. Exposed admin panels and management interfaces

phpMyAdmin, Portainer, Grafana, Jenkins, Kubernetes dashboards — these are frequent, high-value targets precisely because they offer so much control once accessed. Check what’s actually listening on a public interface:

sudo ss -ltunp

-l shows listening sockets only, -t and -u cover TCP and UDP, -n keeps addresses numeric so it’s fast, and -p shows which process owns each socket. For every result bound to 0.0.0.0 or a public IP rather than 127.0.0.1, ask whether it genuinely needs to be internet-reachable. If it doesn’t, either bind it to localhost and reach it over an SSH tunnel or VPN, or put it behind your firewall explicitly rather than trusting “nobody will find it.”

3. Weak or missing SSH hardening

Password authentication on an internet-facing SSH server gets thousands of automated login attempts a day, and it only takes one weak or reused password succeeding. Key-only authentication removes that entire attack class:

# in /etc/ssh/sshd_config (or a drop-in under sshd_config.d/)
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password

PasswordAuthentication no alone isn’t the whole fix: some systems also accept passwords through keyboard-interactive/PAM authentication, a separate mechanism sshd checks independently. Disable KbdInteractiveAuthentication too, or a password prompt can still work even though the setting above says no.

One thing worth checking specifically on cloud images: some ship a cloud-init drop-in under /etc/ssh/sshd_config.d/ that sets password auth back to yes. sshd uses the first value it encounters as it reads through Included files in order, so if that drop-in is processed before your own setting, its value wins — the fix is a config file that sorts earlier (for example a name starting 10- against cloud-init’s typical 50-), not just editing the main file. Either way, check sshd -T | grep -iE "passwordauthentication|kbdinteractiveauthentication" afterward to see the value actually in effect, not just what one file says.

Pair key-only auth with fail2ban watching your auth log for defence in depth. It won’t stop an attacker who already has a valid credential, and its default SSH filter is tuned around failed password attempts rather than public-key failures, but for the everyday password-guessing noise it’s an effective, low-effort layer once tuned to your traffic.

4. Overly permissive cloud storage and security groups

A storage bucket set to public when it only needed to serve one application, or a security group rule that allows a database port from 0.0.0.0/0 instead of from the application server’s specific IP — these are consistently among the most common real-world breach causes, precisely because they’re invisible until someone checks. Audit both explicitly and regularly, not just at initial setup: review bucket/object ACLs for anything broader than intended, and review inbound security group rules for any range wider than the specific source that actually needs access.

5. Exposed .env, .git, and backup files

A misconfigured web server that serves static files from a directory containing .env, .git, or a database backup file hands over database credentials, API keys, and source code to anyone who requests the right path. Confirm these are unreachable:

curl -sI https://example.com/.env
curl -sI https://example.com/.git/config

Both should return 403 or 404, never 200. A HEAD request only confirms whether the path resolves, though — some setups return the same status for HEAD and GET, others don’t, and an application’s catch-all route can return 200 for almost any path without actually serving the file’s contents. If HEAD comes back 200, follow up with an actual GET on a server you administer and check whether real file content comes back before treating it as confirmed exposure. If it is exposed, the fix is the same either way: move the file outside the web root, or add an explicit deny rule in your web server config for dotfiles and common backup extensions.

6. Unpatched software with a known CVE

This is the slowest-moving but most consistently exploited category, because attackers don’t need to find anything new — they scan for known-vulnerable version banners and run existing exploits at scale. A documented patch cadence, even a simple one (“security updates applied within a week of release for internet-facing services”), closes more real attack surface over a year than any single hardening tweak. For deciding what to prioritise, see reading a CVE like a sysadmin.

7. Verbose error pages leaking internal detail

A stack trace, a full file path, a database error with table and column names, a framework’s debug page — all of these hand a would-be attacker a map of your internals for free. Confirm debug mode is off in production for every framework you run (Django’s DEBUG = False, PHP’s display_errors = Off, and their equivalents), and confirm your web server’s default error pages don’t include a version banner giving away exact software versions.

A practical audit order

  1. Run ss -ltunp on every server and account for every listener.
  2. Confirm SSH is key-only and verify the effective config, not just the file you edited.
  3. Check every admin panel and dashboard for a rotated, unique credential.
  4. Review cloud storage ACLs and security group rules for anything broader than necessary.
  5. Confirm .env, .git and backups aren’t web-reachable.
  6. Confirm production error output doesn’t leak internals.
  7. Write down your patch cadence, even informally, and actually follow it.

None of this is exotic. That’s precisely why it’s what actually gets exploited — the sophisticated attack makes the conference talk, the exposed admin panel makes the incident report.

Keep reading

Sources

Related: One concrete example of such a misconfiguration: a wp-config.php backup left in the web root, and how to check for it.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *