{"repro_id":"REPRO-2026-00261","version":6,"title":"BerriAI LiteLLM SQL injection","repro_type":"security","status":"published","severity":"critical","description":"BerriAI LiteLLM is affected by an unauthenticated SQL injection vulnerability. The API endpoint(s) processing proxy or admin requests construct raw SQL queries using attacker-controllable parameters without sufficient parameterization. A remote attacker can send a crafted request to extract or modify the LiteLLM database contents, leading to complete compromise of the API gateway configuration, keys, and logs. CISA KEV indicates active exploitation. Affected versions: LiteLLM proxy/admin API before the patch. Need to reproduce against a vulnerable install (e.g., pip install litellm) and demonstrate SQL injection through a network request. Patch should be identified from upstream commit/PR.","root_cause":"## Summary\n\nBerriAI LiteLLM proxy versions before the upstream parameterization fix contain an API-key authentication SQL injection in the database lookup for virtual keys. During authentication, an attacker-controlled `Authorization: Bearer ...` value reaches the combined verification-token lookup and, in vulnerable LiteLLM 1.83.6, is interpolated directly into a raw SQL string. The reproduction starts real LiteLLM proxy processes with PostgreSQL and proves that a remote request to `GET /model/info` using the injected bearer token `' OR EXISTS(SELECT 1 FROM pg_sleep(3)) --` causes PostgreSQL to execute the injected `pg_sleep(3)` expression and the endpoint to return authenticated model information. The same request against LiteLLM 1.83.7 is rejected immediately because the token is bound as a SQL parameter.\n\n## Impact\n\n- **Package/component affected:** BerriAI LiteLLM proxy authentication and database helper code, specifically the combined verification-token lookup in `litellm/proxy/utils.py` reached from `user_api_key_auth` on protected proxy/API endpoints.\n- **Affected versions:** The reproduced vulnerable package is `litellm==1.83.6`. The fixed negative control is `litellm==1.83.7`. Upstream fix commit identified in the repository is `4dc416ee749122ca91e3bca095217478663419e7` (`fix(proxy): use parameterized query for combined_view token lookup`).\n- **Risk level and consequences:** Critical. A remote unauthenticated attacker can place SQL syntax in the bearer token used by protected endpoints. In the reproduced path this bypasses virtual-key authentication and can execute database expressions. In a real deployment, the same primitive can be used for time/error/boolean SQL injection and can expose or manipulate LiteLLM gateway data such as virtual keys, configuration, and logs, depending on database privileges and SQL payload construction.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact:** Unauthenticated SQL injection through proxy/admin API requests, leading to extraction or modification of LiteLLM database contents and compromise of gateway configuration, keys, and logs.\n- **Reproduced impact from this run:** A production-path remote API request to the real LiteLLM proxy executed attacker-supplied SQL (`pg_sleep(3)`) through the `Authorization` bearer token. The vulnerable proxy returned HTTP 200 with model information, while an unknown-token baseline and the fixed build both returned HTTP 401. PostgreSQL logs captured both the interpolated vulnerable query and the fixed parameterized query.\n- **Parity:** `full` for confirming the remote API SQL-injection vulnerability and fixed-version remediation; `partial` for the broader post-exploitation consequence because this run did not dump or modify sensitive gateway database records beyond demonstrating SQL execution and authentication bypass.\n- **Not demonstrated:** Bulk extraction or modification of production secrets, provider credentials, or logs was intentionally not performed.\n\n## Root Cause\n\nThe vulnerable code constructs a raw SQL query for the combined verification-token view by formatting the token value into the query text:\n\n```python\nWHERE v.token = '{token}'\n```\n\nThe bearer token is attacker controlled. When Python optimization is enabled for product execution, the non-`sk-` assertion in the auth path is not enforced, allowing a token such as:\n\n```text\n' OR EXISTS(SELECT 1 FROM pg_sleep(3)) --\n```\n\nto reach the database helper. In `litellm==1.83.6`, this becomes SQL equivalent to:\n\n```sql\nWHERE v.token = '' OR EXISTS(SELECT 1 FROM pg_sleep(3)) --'\n```\n\nPostgreSQL evaluates the injected `EXISTS(SELECT 1 FROM pg_sleep(3))`, delaying the request and making the `WHERE` condition true for at least one seeded virtual-key row. That row is then treated as the authenticated key object for the protected endpoint.\n\nThe fix changes the helper to accept query arguments and uses a positional parameter:\n\n```python\nWHERE v.token = $1\nresponse = await self._query_first_with_cached_plan_fallback(sql_query, hashed_token)\n```\n\nThe upstream fix commit recorded in the reproduction evidence is:\n\n- `4dc416ee749122ca91e3bca095217478663419e7` — `fix(proxy): use parameterized query for combined_view token lookup`\n\n## Reproduction Steps\n\n1. Use `bundle/repro/reproduction_steps.sh`.\n2. The script:\n   - Reuses or creates the deterministic project cache described by `bundle/project_cache_context.json`.\n   - Installs/reuses `litellm==1.83.6` and `litellm==1.83.7` virtual environments.\n   - Starts a local PostgreSQL instance and two real LiteLLM proxy processes, one vulnerable and one fixed.\n   - Creates a virtual key through each running proxy via `/key/generate` so the verification-token table has a row.\n   - Sends a baseline unknown-token request to `GET /model/info`.\n   - Sends the injected bearer-token request to vulnerable `GET /model/info`.\n   - Sends the identical injected bearer-token request to fixed `GET /model/info`.\n   - Writes `bundle/repro/runtime_manifest.json` and `bundle/repro/result.txt` before exiting.\n3. Expected evidence of reproduction:\n   - Vulnerable request takes approximately three seconds and returns HTTP 200.\n   - Fixed request returns HTTP 401 quickly.\n   - PostgreSQL logs show the vulnerable raw query with `WHERE v.token = '' OR EXISTS(SELECT 1 FROM pg_sleep(3)) --'` and the fixed query with `WHERE v.token = $1` plus the payload only in bound parameters.\n\n## Evidence\n\nPrimary artifacts:\n\n- `bundle/repro/reproduction_steps.sh` — current-run reproducer.\n- `bundle/repro/runtime_manifest.json` — runtime evidence manifest for the API endpoint proof.\n- `bundle/repro/result.txt` — summarized timings/status codes.\n- `bundle/logs/reproduction_steps.log` — full script output.\n- `bundle/logs/source_delta.txt` — fix commit and patch hunk.\n- `bundle/logs/postgres_repro.log` — PostgreSQL query evidence.\n- `bundle/logs/vulnerable_injection.txt` — vulnerable endpoint response.\n- `bundle/logs/fixed_injection.txt` — fixed endpoint response.\n- `bundle/logs/proxy_vulnerable.log` and `bundle/logs/proxy_fixed.log` — LiteLLM proxy runtime logs.\n\nKey current-run results from `bundle/repro/result.txt`:\n\n```text\nresult=confirmed\nbaseline_time=0.013450\nbaseline_code=401\nvuln_injection_time=3.012161\nvuln_injection_code=200\nfixed_injection_time=0.015421\nfixed_injection_code=401\nvulnerable_version=litellm 1.83.6\nfixed_version=litellm 1.83.7\nfixed_commit=4dc416ee749122ca91e3bca095217478663419e7\n```\n\nVulnerable response evidence from `bundle/logs/vulnerable_injection.txt` shows a successful protected endpoint response:\n\n```json\n{\"data\":[{\"model_name\":\"gpt-4o\", ... }]}\n```\n\nFixed response evidence from `bundle/logs/fixed_injection.txt` shows rejection of the exact same token:\n\n```json\n{\"error\":{\"message\":\"Authentication Error, Invalid proxy server token passed... Unable to find token in cache or `LiteLLM_VerificationTokenTable`\",\"type\":\"token_not_found_in_db\",\"param\":\"key\",\"code\":\"401\"}}\n```\n\nPostgreSQL evidence from `bundle/logs/postgres_repro.log` includes the vulnerable query text:\n\n```text\nWHERE v.token = '' OR EXISTS(SELECT 1 FROM pg_sleep(3)) --'\n```\n\nThe same log also includes the fixed query shape and parameter binding:\n\n```text\nWHERE v.token = $1\nDETAIL:  Parameters: $1 = ''' OR EXISTS(SELECT 1 FROM pg_sleep(3)) --'\n```\n\nThe runtime manifest records a real API endpoint path:\n\n```json\n{\n  \"entrypoint_kind\": \"endpoint\",\n  \"entrypoint_detail\": \"GET /model/info with attacker-controlled Authorization Bearer token\",\n  \"service_started\": true,\n  \"healthcheck_passed\": true,\n  \"target_path_reached\": true,\n  \"runtime_stack\": [\"postgresql\", \"litellm-proxy\"]\n}\n```\n\n## Recommendations / Next Steps\n\n- Upgrade LiteLLM proxy deployments to `1.83.7` or later, or to a downstream package containing commit `4dc416ee749122ca91e3bca095217478663419e7` or equivalent parameterization.\n- Ensure all raw SQL helpers use parameterized arguments rather than f-string/interpolated query text.\n- Add regression tests that send bearer tokens containing quotes, comments, and time-based SQL expressions through real protected proxy endpoints and verify they are rejected quickly.\n- Rotate LiteLLM virtual keys, provider credentials, and any secrets stored in the LiteLLM database for deployments that exposed vulnerable versions to untrusted networks.\n- Review PostgreSQL and proxy access logs for suspicious bearer-token values containing quotes, SQL comments, `UNION`, `SELECT`, `pg_sleep`, or similar SQL syntax.\n\n## Additional Notes\n\n- The reproduction script was executed successfully twice consecutively in the current run.\n- The proof uses a real LiteLLM proxy process and a real PostgreSQL database; it does not mock the vulnerable SQL helper.\n- The script bounds all HTTP requests with curl timeouts and writes all diagnostics under `bundle/logs/`.\n- The proof avoids destructive SQL payloads. It uses `pg_sleep(3)` and endpoint response divergence to demonstrate SQL execution and authentication bypass without dumping sensitive database contents.\n","cve_id":"CVE-2026-42271","package":{"name":"BerriAI LiteLLM","ecosystem":"pip","affected_versions":"pip litellm >= 1.81.16, < 1.83.7 (also reported as v1.81.16 through v1.83.6)"},"reproduced_at":"2026-07-07T08:05:02.550337+00:00","duration_secs":10122.0,"tool_calls":711,"handoffs":3,"total_cost_usd":24.607679799999993,"agent_costs":{"coding":0.25256181,"hypothesis_generator":0.10680814,"judge":0.25698600000000005,"repro":17.31814592,"support":0.08737393000000002,"vuln_variant":6.585803999999999},"cost_breakdown":{"coding":{"accounts/fireworks/models/kimi-k2p7-code":0.25256181},"hypothesis_generator":{"accounts/fireworks/models/kimi-k2p7-code":0.10680814},"judge":{"gpt-5.5":0.25698600000000005},"repro":{"accounts/fireworks/models/kimi-k2p7-code":12.464935919999997,"gpt-5.5":4.85321},"support":{"accounts/fireworks/models/kimi-k2p7-code":0.08737393000000002},"vuln_variant":{"gpt-5.5":6.585803999999999}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-07T08:05:39.577493+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":20130,"category":"reproduction_script"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":9040,"category":"analysis"},{"path":"bundle/vuln_variant/reproduction_steps.sh","filename":"reproduction_steps.sh","size":20572,"category":"reproduction_script"},{"path":"bundle/vuln_variant/rca_report.md","filename":"rca_report.md","size":10227,"category":"analysis"},{"path":"bundle/coding/proposed_fix.diff","filename":"proposed_fix.diff","size":2191,"category":"patch"},{"path":"bundle/artifact_promotion_manifest.json","filename":"artifact_promotion_manifest.json","size":12922,"category":"other"},{"path":"bundle/artifact_promotion_report.json","filename":"artifact_promotion_report.json","size":12940,"category":"other"},{"path":"bundle/vuln_variant/root_cause_equivalence.json","filename":"root_cause_equivalence.json","size":1100,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":701,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":980,"category":"other"},{"path":"bundle/logs/reproduction_steps.log","filename":"reproduction_steps.log","size":4145,"category":"log"},{"path":"bundle/logs/postgres_repro.log","filename":"postgres_repro.log","size":169601,"category":"log"},{"path":"bundle/logs/source_delta.txt","filename":"source_delta.txt","size":2370,"category":"other"},{"path":"bundle/logs/proxy_vulnerable.log","filename":"proxy_vulnerable.log","size":4158,"category":"log"},{"path":"bundle/logs/proxy_fixed.log","filename":"proxy_fixed.log","size":4102,"category":"log"},{"path":"bundle/logs/vuln_key_generate.json","filename":"vuln_key_generate.json","size":1462,"category":"other"},{"path":"bundle/logs/fixed_key_generate.json","filename":"fixed_key_generate.json","size":1462,"category":"other"},{"path":"bundle/logs/baseline_unknown.txt","filename":"baseline_unknown.txt","size":271,"category":"other"},{"path":"bundle/logs/vulnerable_injection.txt","filename":"vulnerable_injection.txt","size":3148,"category":"other"},{"path":"bundle/logs/fixed_injection.txt","filename":"fixed_injection.txt","size":296,"category":"other"},{"path":"bundle/repro/result.txt","filename":"result.txt","size":386,"category":"other"},{"path":"bundle/vuln_variant/result.json","filename":"result.json","size":1251,"category":"other"},{"path":"bundle/logs/vuln_variant/reproduction_steps.log","filename":"reproduction_steps.log","size":4469,"category":"log"},{"path":"bundle/logs/vuln_variant/postgres_variant.log","filename":"postgres_variant.log","size":170470,"category":"log"},{"path":"bundle/vuln_variant/variant_manifest.json","filename":"variant_manifest.json","size":3612,"category":"other"},{"path":"bundle/vuln_variant/validation_verdict.json","filename":"validation_verdict.json","size":3497,"category":"other"},{"path":"bundle/vuln_variant/patch_analysis.md","filename":"patch_analysis.md","size":5279,"category":"documentation"},{"path":"bundle/vuln_variant/runtime_manifest.json","filename":"runtime_manifest.json","size":1050,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_version.txt","filename":"fixed_version.txt","size":267,"category":"other"},{"path":"bundle/logs/vuln_variant/source_delta_variant.txt","filename":"source_delta_variant.txt","size":2510,"category":"other"},{"path":"bundle/logs/vuln_variant/vulnerable_api_key_header.txt","filename":"vulnerable_api_key_header.txt","size":3148,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_api_key_header.txt","filename":"fixed_api_key_header.txt","size":304,"category":"other"},{"path":"bundle/logs/vuln_variant/vulnerable_x_litellm_header.txt","filename":"vulnerable_x_litellm_header.txt","size":3148,"category":"other"},{"path":"bundle/logs/vuln_variant/fixed_x_litellm_header.txt","filename":"fixed_x_litellm_header.txt","size":306,"category":"other"},{"path":"bundle/coding/verify_fix.sh","filename":"verify_fix.sh","size":10373,"category":"other"},{"path":"bundle/coding/verify_work/verify_result.txt","filename":"verify_result.txt","size":335,"category":"other"},{"path":"bundle/logs/verify_injection.txt","filename":"verify_injection.txt","size":296,"category":"other"},{"path":"bundle/logs/verify_valid_key.txt","filename":"verify_valid_key.txt","size":3148,"category":"other"},{"path":"bundle/coding/summary_report.md","filename":"summary_report.md","size":4885,"category":"documentation"}]}