The short version
Four settings in one Docker Compose block combined into a full compromise. Individually each looks reasonable, and three of them appear in tutorials and sample configurations. Together they published an authenticated database administration tool to the open internet.
The whole attack took three seconds, start to finish, and was carried out by an automated scanner using python-requests. This was not a targeted attack. It was a machine finding an open door on a schedule.
The four settings
1. "5050:80" binds to every interface. Docker's short port syntax defaults to 0.0.0.0, so the container is reachable from the internet unless a firewall says otherwise. Writing "127.0.0.1:5050:80" would have limited it to the host.
2. SERVER_MODE: 'False' is the critical one. pgAdmin has two modes. Server mode is multi-user and asks for a login. Desktop mode assumes it is running on your own laptop where the operating system is the security boundary, so it does not authenticate at all. In a container published to the internet, every visitor is automatically the administrator.
This is why the logs contained no failed logins and no brute force. There was no login step to fail. It is also why this class of incident is easy to miss when reviewing logs: the attacker's requests look exactly like an administrator's.
3. MASTER_PASSWORD_REQUIRED: 'False' removes the gate protecting saved connection credentials.
4. PGADMIN_SERVER_JSON_FILE pre-loads a database connection, and the accompanying pgpass file holds the database password in plaintext. So the automatic administrator arrived to find a working database connection already configured.
The default password on line five is almost a footnote. Once authentication is disabled entirely, the password protects nothing.
What the attacker did
The execution step abuses pgAdmin's import/export feature, which runs a binary on the server as a background job. It is a documented capability, and the CVE-2024-3116 class of issues covers exactly this path being reachable by someone who should not have it. With authentication disabled, "someone who should not have it" is the entire internet.
The cleanup step is worth noting. The scanner deleted the server object it created, which removes the obvious trace from the pgAdmin UI. The request log still had everything, and the miner binary's creation timestamp lined up with the request timestamps to the second.
What was and was not affected
The database itself came through intact: no attacker-created roles, no superusers, no external connections, no sign of tampering. The miner wanted CPU, not data, which is typical for opportunistic scanning.
That is luck rather than design. The pgpass file containing the database password was readable by the automatic administrator, so the credential must be treated as disclosed regardless of whether it was used. Rotating it is not optional.
What to do
If you run pgAdmin in a container:
- Never set
SERVER_MODE: 'False'on anything reachable from a network. It is a laptop setting. - Bind admin tools to localhost (
127.0.0.1:5050:80) and reach them over SSH port-forwarding or a VPN. An administration interface has no business on a public port. - Do not pre-load database credentials into the container. Convenience for you is convenience for whoever arrives first.
- If you must expose it, put authentication in front of it and keep
MASTER_PASSWORD_REQUIREDon.
More generally: treat every published port as an inventory item. docker ps shows what you published; compare it against what you meant to publish. In this case a single docker port review would have caught it, at any point, before the scanners did.
If you think it happened to you: stop the container rather than deleting it first, so you keep the evidence. Preserve the request logs, hash any unexpected binaries, and check both the database roles and the active connections. Then rotate every credential the container could read, and remove the tool rather than reconfiguring it if you do not actually need it running.
The uncomfortable part
Three of these four settings exist to make life easier during development, and they show up in blog posts and starter templates because they genuinely do. The failure was not exotic. It was a development-shaped configuration reaching an internet-shaped environment, which is one of the most common ways systems are lost.
The defence is boring and effective: know what is published, and assume anything on a public port will be found by an automated scanner within days.
Adminor works with attack surface monitoring and security testing. If you want to know what your organisation currently has published to the internet, get in touch.