// DevOps

A new server isn't a blank slate. It's an unlocked door.

Published on 2026-09-22

A few days ago I investigated a production server compromise. Not a theoretical one — a real one, with a Monero miner, a C2 agent and a backdoor. The attack was completely automated. No one was specifically hunting that server — a bot continuously scans the entire internet, tries open databases with default passwords and does its job.

From the server appearing on the network to the first attack it took less than six hours. To a successful compromise — less than a day.

Three conditions that made it possible: PostgreSQL was exposed, the password was postgres, the firewall was not enabled. That’s it. Nothing else was required.


Why this happens even to experienced people

There is a certain psychology around a new server. You just spun it up, everything works, the application responds — and you move on to the next task. Security feels like something that can be set up later. Later — when there is time. Later — before production. Later — after the release.

Later never comes. Or it does, but only in the form of top showing /tmp/mysql at 400% CPU.

A new server on the internet is not a blank slate. It’s an unlocked door in a house that already stands on a busy street. Scanners find it within hours, not days.


What actually happened

The attacker found an open port 5432 on the internet. They ran a brute force. The password postgres — that’s not even brute forcing, it’s the first line in any dictionary. They gained superuser access to PostgreSQL.

Next comes a legitimate PostgreSQL feature — COPY FROM PROGRAM. It allows a superuser to execute an arbitrary shell command directly from an SQL query. The attacker passed a bash script to it encoded in base64, which downloaded and launched a dropper.

But the smartest part was the persistence mechanism. Event triggers were added to the database — triggers that fire on every DDL operation (CREATE TABLE, ALTER, DROP…). On each such event they quietly recreated a superuser role with a password known to the attacker. So even if an admin noticed and removed the malicious role — the next migration or CREATE INDEX would automatically restore it.

Elegant and truly nasty.


Three rules that cut off 99% of such attacks

I’m intentionally not writing “ten rules” or a “complete checklist”. Not because the rest isn’t important — but because these three rules close off the majority of automated attacks that actually happen in the wild.

Databases must not be accessible from the internet. PostgreSQL, MySQL, Redis, MongoDB — none of these are designed for direct external access. In a docker-compose.yml the line "5432:5432" for a database is almost always a mistake. The application will reach the database inside the Docker network by hostname. Externally that port is needed by no one except attackers.

If you need access for development — bind only to localhost:

yaml
ports:
  - "127.0.0.1:5432:5432"

For production — remove ports entirely.

Default passwords are not passwords. postgres, password, root, admin — these are the first lines in any brute-force dictionary. A scanner will try them in seconds, not hours. Generate a password at deploy time:

bash
POSTGRES_PASSWORD=$(openssl rand -base64 32)

Or use secret management: HashiCorp Vault, AWS Secrets Manager, Doppler. The main thing — never commit passwords to the repository and don’t leave default values in .env.

The firewall must be enabled from the first minute. Not before release. Not after configuration. From the first minute after creating the server. The correct model — deny everything, open only what is necessary:

bash
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw enable

After that, open specific ports as needed. Not the other way around.


Monitoring you should add

The three rules above are about prevention. But if you also want to see what’s happening, add minimal logging to PostgreSQL. By default it doesn’t write successful connections or DDL statements. That means you have no visibility into what is happening with the database.

In postgresql.conf:

log_connections = on
log_disconnections = on
log_statement = 'ddl'

And a simple cron script to check for anomalies:

bash
#!/bin/bash
SUPERUSERS=$(psql -U postgres -t -c "SELECT count(*) FROM pg_user WHERE usesuper = true AND usename != 'postgres';")
if [ "$SUPERUSERS" -gt 0 ]; then
    echo "ALERT: unauthorized superusers detected" | mail -s "Security Alert" admin@example.com
fi

This is already enough to notice something suspicious before top does.


Why this matters more than it seems

According to Shodan, there are over 800,000 PostgreSQL instances open on the internet. A significant portion use default or weak passwords. Nobody is hacking them manually — these are fully automated scanners running around the clock. They do not pick victims by business size or data value. They just go down the list of open ports.

Your server is no exception. It’s already on that list. The only question is whether there’s a lock on the door.

Protection against this doesn’t require complex technologies, expensive tools, or deep security expertise. It requires three things you can do in ten minutes on first logging into the server. Just don’t postpone them.

More about each defensive layer — in the “Protecting a Linux server” series: UFW firewall, Fail2ban, CrowdSec and audit with Lynis.

// Reviews

Related reviews

I came with an expensive request to configure a VPS server, but during the consultation Mikhail suggested a much simpler, more affordable solution. In the end I saved time and money. Mikhail — a true expert who works for the client's result, not for the fee. I recommend him!

I came with an expensive request to configure a VPS server, but during the consultation Mikhail suggested a much simpler and more cost-effective solution. In the end I saved budget and time. Mikhail — a true expert who …

kfhzasorin

VPS setup, server setup

2026-05-12 · ★ 5/5

// Contact

Need help?

Get in touch with me and I'll help solve the problem

I reply within one business day (03:00-13:00 GMT)

Или оставьте заявку здесь:

Confirm that you are not a bot.

Write and get a quick reply