Help! My database was compromised!

A detailed look at an active PostgreSQL and MySQL Ransomware bot

The other day, I noticed this tweet mentioning how someone's test PostgreSQL database was compromised. This intrigued me and made me wonder how common this is. So, I decided to run an experiment. Let's run a simple PostgreSQL server on a DigitalOcean VM and see what happens! And wow, I wasn't disappointed.

To my surprise, it only took a few hours to get compromised; in fact, I re-ran the experiment a few times and reproduced the compromise several times a day. It's a scary world out there!

Compromised Database

In all cases, my database was wiped out (drop database). Interestingly, the attacker left a note in a newly created database called readme_to_recover with instructions for how to get our data back...

So this made me curious: what exactly happened, and how common is it?

What exactly happened?

To better understand exactly what happened during these events, I put my PostgreSQL database behind a simple custom PostgreSQL proxy. This authenticates the user and logs all queries. Doing this will give us some good insights into exactly what queries were being executed by the attacking bot.

In this experiment, we made our test PostgreSQL database available from anywhere on the Internet, with username: postgres and password: password. It had an example database called books, with a few tables and some dummy data.

With this setup, we should learn more about what exactly happened. I didn't have to wait long; soon after starting my new setup, the bot came knocking again!
The first log line shows me where the attack was coming from:

2024/01/11 19:02:10 New connection accepted from

This is an IP from a hosting provider out of The Netherlands, AS394711, also known as Limenet. The user authenticated as user: postgres and password: password

With our lightweight proxy, we can now see exactly what queries are being executed. The first few queries we see are intended to explore what’s in our database.

SELECT datname FROM pg_database;

This helps the bot to identify all available databases in our PostgreSQL server.

Now that the bot knows what databases are available, it next tries to determine what tables are available for each database and what they look like. Then, it takes a snapshot of each table in the database before dropping both the tables and databases.

These are the exact queries we recorded, all wrapped in a transaction per table.  This was repeated for all tables in my books database.

BEGIN SELECT pg_database_size('books') SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' AND table_catalog = 'books'; SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'authors'; SELECT * FROM authors LIMIT 20; DROP TABLE IF EXISTS authors CASCADE; COMMIT

After processing all tables, it then dropped the database like this:


Before continuing, it also executed this query:

SELECT pg_terminate_backend( FROM pg_stat_activity WHERE pg_stat_activity.datname <> 'postgres' AND pid <> pg_backend_pid();

This query terminates all backend processes connected to any database except the 'postgres' database and excludes the process running the query itself. It is likely used to terminate other active connections to the database. Possibly to prevent administrators or automated systems from interrupting the attack.

A note was left - Ransom Demand

As a last step, the attacker left us a note in a newly created database called readme_to_recover.

CREATE DATABASE readme_to_recover TEMPLATE template0;

And then uses this new database to add a readme like below

BEGIN CREATE TABLE readme (text_field VARCHAR(255)); INSERT INTO readme (text_field) VALUES ('All your data is backed up. You must pay 0.007 BTC to 164hyKPAoC5ecqkJ2ygeGoGFRcauWRLujV In 48 hours, your data will be publicly disclosed and deleted. (more information: go to'); INSERT INTO readme (text_field) VALUES ('After paying send mail to us: and we will provide a link for you to download your data. Your DBCODE is: 3UZDL'); COMMIT

So, a select text_field from readme shows us how to recover our data.

All your data is backed up. You must pay 0.007 BTC to 164hyKPAoC5ecqkJ2ygeGoGFRcauWRLujV In 48 hours, your data will be publicly disclosed and deleted. (more information: go to paying send mail to us: and we will provide a link for you to download your data. Your DBCODE is: 3UZDL

It conveniently informs us that we can get a copy of our data back for 0.007 BTC (about $327 USD at the time of writing this article). The data will be publicly disclosed and deleted if we don't pay!

But that's a lie; we know the bot didn't take all our data! The query logs clearly show that while it did a select * for each table, it also included a LIMIT 20, which means that the bot only selected 20 rows for each table. So even though it's claiming it backed up all the data, that's clearly not true. It can also not disclose all data, as all it took was the first 20 rows for each table. This also means that paying the ransom of 0.007 BTC will be useless.

Queries executed by the attacking ransom bot

How much money is made?

Taking a closer look at the Bitcoin address specified in the ransom note reveals the activity. Five separate transactions were made to this address in the last few days, combined, bringing in just over $2,400 USD. Notably, each time funds were transferred to it, they were swiftly moved to another wallet. Unfortunately, these victims would never receive their data back.

The same Bot is targeting MySQL databases

Amid my investigation, an intriguing parallel surfaced involving MySQL databases. The same bot attacks MySQL databases and comes from various IP addresses within the same /24 range. However, there were subtle differences in its approach. Notably, when sifting through the data, the bot imposed a LIMIT of 10 rows, again stopping short of a complete data extraction. Following this, it systematically drops all tables and databases. In a move mirroring its PostgreSQL tactics, it established a new database named RECOVER_YOUR_DATA, which has a table of the same name, RECOVER_YOUR_DATA. This table contains the same ransomware message we observed earlier, including the same Bitcoin wallet address. However, curiously, the ransom price for MySQL database is 0.017 BTC (USD $732), about double the PostgreSQL price.

As a last step,  the bot attempted to bring the MySQL server to a halt using the 'SHUTDOWN' command - a clear sign of its calculated and destructive intent, something that will undoubtedly get your attention.

Exposing your database by accident is easier than you think

Ok, I know what you're thinking: who the heck exposes their database to the Internet like that? You got what you deserved!

Well, yes and no! Unfortunately, exposing your database publicly to the Internet is asking for trouble! So ideally, you don't do that or find a way to protect yourself from drive-by attacks like these. At least set a strong password.
So, although it's not the best idea to leave your database wide open, it's also not as uncommon as you might think. Perhaps you've done it yourself; sometimes it's just convenient.

A quick check at, shows us that there are about 833,666 publicly accessible PostgreSQL servers available and over 3.2 Million MySQL databases! Lots of potential targets. Most of them run in the public cloud providers like AWS, GCP, DigitalOcean, etc. But, interestingly the number two on the list is a Polish provider called (AS12824), for which shodan reports over 82,000 publicly reachable PostgreSQL servers.

It’s not surprising to see many open database services in the public cloud. If you run your database in say DigitalOcean or even AWS, then these cloud providers don’t always make it easy to access your database from your desktop, or even a workload running in a different region or provider. You may have no other option than to open it from anywhere. And so, while bad practice,  it’s not all that surprising that there are that many open databases.

Also, for Docker users, it's important to know that using docker run -p to publish a container's port alters your iptables rules for DNAT-based port forwarding. This Docker feature manages external communication for containers, overriding any default deny settings in your iptables INPUT table, thus making the port accessible publicly.

A better way to expose your database

If you need help with making your database available in an easy, low-friction way to just the users who should have access but don't want to expose your database to the whole world, then take a look at Border0!

The video below demonstrates how easy it is to make your PostgreSQL database available using Border0.

Using Border0 you can make your database available to your fellow team members, contractors and anyone you determine should have access. All without the need for a VPN, while still allowing you to block access from the whole Internet. All while just needing your single sign-on credentials. So best of both worlds!

So why settle for the outdated confines of conventional access management? Step into the enhanced adaptability and governance that Border0's policies offer and leverage the transformation in access management. Take the opportunity to explore the advantages of Border0 firsthand by registering for our free, full-featured community edition today.

Ready to level up your security?