lmdeploy's `_is_safe_url` check can be bypassed because Python's urlparse and the requests library disagree on which host a URL like `http://127.0.0.1:6666\@1.1.1.1` points to. The validator sees a public IP, but the HTTP client connects to the internal address. That lets an unauthenticated remote attacker reach loopback and private services (cloud metadata endpoints, internal admin APIs) from the inference server, with high confidentiality impact (CVSS 7.5). No KEV listing, public exploit, or Nuclei template is recorded and EPSS is not yet available, but the PoC is a single string in the advisory and the fix is trivial to reverse-engineer. Blast radius is modest (1 downstream dependent), though 9 other CVEs in the same package suggest a weak security track record. Upgrade lmdeploy to 0.15.0 (affected: >= 0.12.3, < 0.15.0) and restrict egress from inference hosts to internal networks and metadata services.
What is the risk?
High, driven by network exploitability, no authentication, and low complexity (AV:N/AC:L/PR:N/UI:N). Impact is confidentiality only (C:H/I:N/A:N), but exposure of cloud credentials via metadata endpoints can escalate well beyond the inference service. Likelihood is raised by the one-line public PoC. No active exploitation (KEV: no), no public exploit module or scanner template, and EPSS is not available, so the immediate probability of mass exploitation is unquantified. Exposure is limited to deployments that accept user-supplied URLs (for example image or media URLs in multimodal requests) on internet-reachable or multi-tenant lmdeploy servers.
How does the attack unfold?
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| LMDeploy | pip | >= 0.12.3, < 0.15.0 | 0.15.0 |
Do you use LMDeploy? You're affected.
How severe is it?
What is the attack surface?
What should I do?
1 step1) Upgrade lmdeploy to 0.15.0 or later. 2) Until patched, do not expose the lmdeploy API server to untrusted users, and put it behind an authenticated gateway. 3) Enforce egress filtering on inference hosts: block access to 127.0.0.0/8, RFC1918 ranges, 169.254.169.254 and other metadata endpoints. 4) Enforce IMDSv2 (hop limit 1) or equivalent on cloud instances and use least-privilege instance roles. 5) Detection: search request logs and API payloads for URLs containing backslashes or
\@, userinfo (@) components, or unexpected outbound connections from the inference process to internal addresses. 6) Pin to an allowlist of trusted media hosts if URL fetching is required.
How is it classified?
Which compliance frameworks are affected?
This CVE is relevant to:
Frequently Asked Questions
What is GHSA-39wr-7q6h-cf68?
lmdeploy's `_is_safe_url` check can be bypassed because Python's urlparse and the requests library disagree on which host a URL like `http://127.0.0.1:6666\@1.1.1.1` points to. The validator sees a public IP, but the HTTP client connects to the internal address. That lets an unauthenticated remote attacker reach loopback and private services (cloud metadata endpoints, internal admin APIs) from the inference server, with high confidentiality impact (CVSS 7.5). No KEV listing, public exploit, or Nuclei template is recorded and EPSS is not yet available, but the PoC is a single string in the advisory and the fix is trivial to reverse-engineer. Blast radius is modest (1 downstream dependent), though 9 other CVEs in the same package suggest a weak security track record. Upgrade lmdeploy to 0.15.0 (affected: >= 0.12.3, < 0.15.0) and restrict egress from inference hosts to internal networks and metadata services.
Is GHSA-39wr-7q6h-cf68 actively exploited?
No confirmed active exploitation of GHSA-39wr-7q6h-cf68 has been reported, but organizations should still patch proactively.
How to fix GHSA-39wr-7q6h-cf68?
1) Upgrade lmdeploy to 0.15.0 or later. 2) Until patched, do not expose the lmdeploy API server to untrusted users, and put it behind an authenticated gateway. 3) Enforce egress filtering on inference hosts: block access to 127.0.0.0/8, RFC1918 ranges, 169.254.169.254 and other metadata endpoints. 4) Enforce IMDSv2 (hop limit 1) or equivalent on cloud instances and use least-privilege instance roles. 5) Detection: search request logs and API payloads for URLs containing backslashes or `\@`, userinfo (`@`) components, or unexpected outbound connections from the inference process to internal addresses. 6) Pin to an allowlist of trusted media hosts if URL fetching is required.
What systems are affected by GHSA-39wr-7q6h-cf68?
This vulnerability affects the following AI/ML architecture patterns: model serving, LLM inference servers, multimodal inference pipelines, cloud-hosted GPU inference.
What is the CVSS score for GHSA-39wr-7q6h-cf68?
GHSA-39wr-7q6h-cf68 has a CVSS v3.1 base score of 7.5 (HIGH).
What is the AI security impact?
Affected AI Architectures
MITRE ATLAS Techniques
AML.T0036Data from Information RepositoriesAML.T0040AI Model Inference API AccessAML.T0049Exploit Public-Facing ApplicationCompliance Controls Affected
What are the technical details?
Original Advisory
### Summary The URL checking logic in lmdeploy has a logical flaw that could be bypassed by attackers, leading to SSRF attacks. ### Details The current lmdeploy project uses `_is_safe_url` to validate the input URL. The main logic is to perform security checks on the host portion of the URL extracted by urlparse to prevent SSRF attacks. <img width="943" height="836" alt="QQ20260416-203956-16-1" src="https://github.com/user-attachments/assets/042faad1-7458-444a-bbc9-525c772b0a4d" /> However, there are indeed differences in parsing between urlparse and the library that actually sends the request. Currently, almost all application scenarios in this project involve first using `_is_safe_url` for URL validation, and then using requests.Session().get to send the request. <img width="1086" height="576" alt="QQ20260416-204053-16-2" src="https://github.com/user-attachments/assets/7ffb8a69-b155-483a-90be-016c53e6387a" /> The core issue: `urlparse()` and `requests` disagree on which host a URL like `http://127.0.0.1:6666\@1.1.1.1` points to: - `urlparse()` treats `\` as a regular character and `@` as the userinfo-host delimiter, so it extracts hostname as 1.1.1.1 (public) - `requests` treats `\` as a path character, connecting to `127.0.0.1` (internal) Below is a test code I wrote following the code. ``` from urllib.parse import urlparse import ipaddress import socket import requests def _is_safe_url(url: str) -> tuple[bool, str]: """Check if the URL is safe to fetch (not internal/private).""" try: parsed = urlparse(url) if parsed.scheme not in ("http", "https"): return False, f"Unsupported scheme: {parsed.scheme}" hostname = parsed.hostname if not hostname: return False, "Could not parse hostname from URL" # check all IPs (IPv4 + IPv6) using getaddrinfo try: infos = socket.getaddrinfo(hostname, None) except socket.gaierror: return False, "Hostname resolution failed" for info in infos: ip = ipaddress.ip_address(info[4][0]) # block any IP that is not globally routable (covers private, loopback, # link-local, multicast, reserved, unspecified, etc.) if not ip.is_global: return False, f"Blocked non-global IP detected: {ip}" return True, "URL is safe" except Exception as e: return False, f"URL validation failed: {str(e)}" # url = "http://127.0.0.1:6666" url = "http://127.0.0.1:6666\@1.1.1.1" is_safe, reason = _is_safe_url(url) if not is_safe: raise ValueError(f"URL is blocked for security reasons: {reason}") fetch_timeout = 10 client = requests.Session() client.max_redirects = 3 response = client.get(url, timeout=fetch_timeout, allow_redirects=True) ``` When an attacker uses http://127.0.0.1:6666/, the existing detection logic can detect that this is an internal network address and block it. <img width="1286" height="195" alt="QQ20260416-204234-16-3" src="https://github.com/user-attachments/assets/b921ff01-3b9f-49a5-a410-bd21fe42f9c9" /> However, when an attacker uses `http://127.0.0.1:6666\@1.1.1.1`, the detection logic resolves the host to `1.1.1.1`, which is a public IP address, thus passing the verification. But in the actual request process, this URL is forwarded by requests.get to `http://127.0.0.1:6666/`, bypassing the detection and achieving an SSRF attack. <img width="2064" height="154" alt="QQ20260416-204319-16-4" src="https://github.com/user-attachments/assets/5da18f35-f400-46e6-9bf3-1330ba424b02" /> ### PoC ``` http://127.0.0.1:6666\@1.1.1.1 ``` ### Impact SSRF
Exploitation Scenario
An attacker sends a multimodal inference request to an exposed lmdeploy server with an image URL such as `http://169.254.169.254:80\@1.1.1.1`. The `_is_safe_url` check parses the host as 1.1.1.1, resolves it to a global IP, and approves the URL. The requests library then connects to the cloud metadata service, and the response is processed by the server. The attacker retrieves temporary IAM credentials or probes internal services, then uses those credentials to pivot into model storage buckets, data stores, and the wider cloud environment.
Weaknesses (CWE)
CWE-436 — Interpretation Conflict: Product A handles inputs or steps differently than Product B, which causes A to perform incorrect actions based on its perception of B's state.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NReferences
Timeline
Related Vulnerabilities
CVE-2026-1197069.8LMDeploy: unauthenticated RCE via pickle in /update_weights
Same package: lmdeployCVE-2026-768509.8LMDeploy: RCE via unauth pickle deserialization
Same package: lmdeployCVE-2025-664559.8LMDeploy: pickle deserialization RCE in DistServe
Same package: lmdeployCVE-2025-599539.8LMDeploy: unsafe pickle deserialization allows RCE
Same package: lmdeployCVE-2025-677298.8lmdeploy: Deserialization enables RCE
Same package: lmdeploy