Detect qwed-mcp Unsafe SymPy parse_expr() Remote Code Execution (CVE-2026-55546) in Google Chronicle
Detects exploitation of CVE-2026-55546, a critical (CVSS 9.8) code-injection vulnerability (CWE-94) in the qwed-mcp Python package (< 0.2.1). qwed-mcp is a Model Context Protocol (MCP) server exposing math tools that pass user-supplied expression strings directly to SymPy's parse_expr() without restricting the evaluation namespace or transformations. Because parse_expr() will evaluate Python-callable constructs, an attacker can craft a math expression that invokes arbitrary Python (e.g. via __import__, object attribute traversal, or eval-reachable builtins), achieving remote code execution in the process hosting the MCP server. This detection surfaces the MCP server process spawning unexpected child processes (shell/interpreter/network tools), suspicious inbound tool-call payloads containing Python injection primitives, and installation of vulnerable package versions.
MITRE ATT&CK
- Tactic
- Initial Access Execution
YARA-L Detection Query
rule qwed_mcp_parse_expr_rce {
meta:
author = "Argus"
description = "qwed-mcp SymPy parse_expr() RCE (CVE-2026-55546) child process spawn"
severity = "CRITICAL"
events:
$e.metadata.event_type = "PROCESS_LAUNCH"
re.regex($e.principal.process.parent_process.command_line, `(?i)qwed[-_]?mcp`)
$e.principal.process.parent_process.file.full_path = /python|uvicorn|gunicorn/ nocase
$e.target.process.file.full_path = /\/(sh|bash|zsh|dash|curl|wget|nc|ncat|whoami|id)$|powershell\.exe|cmd\.exe/ nocase
not $e.target.process.command_line = /pip install|pytest/ nocase
condition:
$e
} Chronicle YARA-L rule detecting the qwed-mcp Python process launching shells or network tools consistent with parse_expr() code injection.
Data Sources
Required Tables
False Positives & Tuning
- Package install/upgrade automation shelling out
- Developer debugging subshells
- Container init sh wrappers around the server
Other platforms for CVE-2026-55546
Testing Methodology
Validate this detection against 3 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Simulate parse_expr injection spawning a shell
Expected signal: Process creation event: python3 parent spawning sh -> id, with command line referencing __import__/os.system
- Test 2Malicious MCP tool-call payload with injection primitives
Expected signal: MCP application log records a tool-call whose expression argument contains __class__/__subclasses__
- Test 3Detect vulnerable qwed-mcp version installed
Expected signal: Process execution of pip show; inventory output showing qwed-mcp version
References (5)
- https://github.com/QWED-AI/qwed-mcp/security/advisories/GHSA-mw6r-2hvm-4rp2
- https://github.com/QWED-AI/qwed-mcp/pull/22
- https://github.com/QWED-AI/qwed-mcp/commit/362e61892052e250c56cb1ee852024d6f98c467b
- https://github.com/QWED-AI/qwed-mcp/releases/tag/v0.2.1
- https://github.com/advisories/GHSA-mw6r-2hvm-4rp2
Response Playbook
Triage
- Confirm the alerting host runs qwed-mcp and check the installed version: `pip show qwed-mcp` — versions < 0.2.1 are vulnerable.
- Retrieve the parent qwed-mcp process command line and the child process spawned; determine whether the child is a shell, interpreter, or network tool with no legitimate business justification.
- Pull the MCP server's request/tool-call logs around the alert time and inspect the math-expression argument for Python injection primitives (__import__, __class__, __subclasses__, os., subprocess, eval, exec, lambda).
- Identify the source of the malicious tool call (client IP, MCP session ID, upstream LLM agent) to scope whether exploitation was internal or externally reachable.
Containment
- Isolate the affected host from the network (EDR containment) to prevent attacker follow-on actions and C2.
- Stop the qwed-mcp service and block inbound access to its listening port until it is upgraded to >= 0.2.1.
- Rotate any credentials, API keys, or tokens accessible to the qwed-mcp process, as they must be considered compromised.
Evidence Collection
- Capture the full process tree (parent qwed-mcp and all descendants), including command lines, PIDs, and timestamps.
- Preserve qwed-mcp application logs and MCP tool-call payloads containing the malicious expression.
- Collect network connection logs (netstat/EDR) for outbound connections initiated by the qwed-mcp process or its children.
- Snapshot the host memory and disk if RCE is confirmed for forensic analysis of dropped payloads.
Escalation Criteria
- !Escalate to incident response if a child process established an outbound network connection or dropped/executed a secondary payload.
- !Escalate if the qwed-mcp instance was internet-facing or reachable by untrusted MCP clients, indicating potential external compromise.
- !Escalate if credential theft, lateral movement, or persistence artifacts are found following the initial code execution.
Investigation Guide
Related Techniques
Forensic Artifacts
- >
qwed-mcp application/tool-call logs containing the malicious math expression - >
Process creation records (Sysmon EID 1 / auditd execve) showing the qwed-mcp parent spawning unexpected children - >
Outbound network connection logs from the qwed-mcp process - >
Any files written to temp/working directories by spawned child processes
Tuning Guidance
Baseline the legitimate child processes of your qwed-mcp deployment (e.g., health-check scripts, container entrypoint wrappers) and add them to an allowlist. If qwed-mcp runs inside a container with a known static command line, tighten the parent-command-line match to that exact invocation. Exclude known package-management and test activity (pip, pytest, uv) by user and path. After upgrading all instances to >= 0.2.1, downgrade severity to informational and retain the rule for regression detection.
Hunting Queries
Hunt across the estate for any qwed-mcp process spawning shells or network tooling, grouping by host and child process to surface exploitation clusters.
DeviceProcessEvents | where InitiatingProcessCommandLine has_any ("qwed-mcp","qwed_mcp") | where FileName in~ ("sh","bash","curl","wget","nc","python3","whoami","id") | summarize count() by DeviceName, FileName, ProcessCommandLine, bin(Timestamp, 1h) index=* ("qwed-mcp" OR "qwed_mcp") (process_name IN ("sh","bash","curl","wget","nc","whoami","id")) | stats count by host, process_name, process | sort - count Atomic Red Team Tests
Reproduces the RCE effect by having a Python process (standing in for qwed-mcp) evaluate an untrusted SymPy-style expression that reaches os.system, spawning a shell.
Command
python3 -c "from sympy import parse_expr; import sympy; parse_expr('__import__(\"os\").system(\"id > /tmp/qwed_poc.txt\")', evaluate=True)" 2>/dev/null; cat /tmp/qwed_poc.txt Cleanup
rm -f /tmp/qwed_poc.txt Expected Telemetry
Process creation event: python3 parent spawning sh -> id, with command line referencing __import__/os.system
Expected Detection
KQL/SPL/EQL rules fire on the python parent spawning sh/id child process
Sends a crafted math expression containing Python injection primitives to a locally running qwed-mcp instance (lab) to generate a suspicious tool-call log entry.
Command
curl -s -X POST http://127.0.0.1:8000/mcp -H 'Content-Type: application/json' -d '{"tool":"evaluate","expression":"().__class__.__base__.__subclasses__()"}' Cleanup
echo 'no cleanup required; review and purge test tool-call logs if desired' Expected Telemetry
MCP application log records a tool-call whose expression argument contains __class__/__subclasses__
Expected Detection
Log-based hunting query flags injection primitives in the expression payload
Enumerates the installed qwed-mcp package version to identify hosts running affected releases (< 0.2.1).
Command
pip show qwed-mcp 2>/dev/null | grep -i version || echo 'qwed-mcp not installed' Cleanup
echo 'no cleanup required' Expected Telemetry
Process execution of pip show; inventory output showing qwed-mcp version
Expected Detection
Vulnerability management correlation flags qwed-mcp version below 0.2.1