Security · Red Team
Default Postgres credentials are an open door
A public form product. A database bound to the internet. A ransomware bot that did not need a clever exploit.
Simple Form is a Typeform-style builder: public submissions, analytics, exports, embeds, email, plan limits. I also put rate limiting and a honeypot on the public path. That was the part I thought of as “security.”
The part I treated as plumbing was the database. PostgreSQL was reachable from the internet. Credentials were the kind you use when you just want the thing to boot.
What happened
A ransomware bot found that instance. No custom payload. No insider. It scanned, logged in, and treated the data as leverage.
That is the boring version of a breach. Default credentials plus a public port. The application layer never got a vote.
The attacker’s view
Looking at it from the other side, the attack was cheap. Internet-wide scanners sweep for port 5432 constantly. Anything that answers gets a short list of common usernames and passwords. A login that works gets scripted: dump or drop the tables, leave a ransom note, move on to the next IP.
No one picked my server. It was just next on the list. That’s what most real compromises look like: automation plus a default, not a zero-day.
Running that chain myself against my own box, in a controlled way, is the first self-assessment I’m writing up.
What changed
I locked the database to loopback. The app on the same machine still talks to Postgres. The internet does not. Credentials became real secrets, not a convenience string in a compose file.
The rest of the box already had Docker, Caddy, PM2, and scripts. Those do not help if the data store is a public login form.
App-layer controls do not save you from an open database.
What I took from it
This isn’t a pentest story and I won’t dress it up as one. It’s a deployment mistake with a real attacker on the other end, and it’s the reason I started studying the other side.
It is also why I care about system hardening now: bind addresses, credential hygiene, and the gap between “the feature works” and “the machine is closed.” That gap is where a lot of small products live.
If you ship a public app, assume scanners will find every port you forgot. Then make the database invisible from outside.