GHSA-V359-JJ2V-J536
Vulnerability from github – Published: 2026-03-09 19:55 – Updated: 2026-03-10 18:39
VLAI?
Summary
vLLM has SSRF Protection Bypass
Details
Summary
The SSRF protection fix for https://github.com/vllm-project/vllm/security/advisories/GHSA-qh4c-xf7m-gxfc can be bypassed in the load_from_url_async method due to inconsistent URL parsing behavior between the validation layer and the actual HTTP client.
Affected Component
- File:
vllm/connections.py - Function:
load_from_url_async
Vulnerability Details
Root Cause
The SSRF fix uses urllib3.util.parse_url() to validate and extract the hostname from user-provided URLs. However, load_from_url_async uses aiohttp for making the actual HTTP requests, and aiohttp internally uses the yarl library for URL parsing.
These two URL parsers handle backslash characters (\) differently:
| Parser | Input URL | Parsed Host | Parsed Path | Behavior |
|---|---|---|---|---|
urllib3.parse_url() |
https://httpbin.org\@evil.com/ |
httpbin.org |
/%5C@evil.com/ |
URL-encodes \ as %5C, treats \@evil.com/ as part of the path |
yarl (via aiohttp) |
https://httpbin.org\@evil.com/ |
evil.com |
/ |
Treats \ as part of userinfo (user: httpbin.org\), the @ acts as the userinfo/host separator |
Attack Scenario
# Attacker provides this URL
malicious_url = "https://httpbin.org\\@evil.com/"
# 1. Validation layer (urllib3.parse_url)
parsed = urllib3.util.parse_url(malicious_url)
# parsed.host == "httpbin.org" ✅ Passes validation
# 2. Actual request (aiohttp with yarl)
async with aiohttp.ClientSession() as session:
async with session.get(malicious_url) as response:
# Request actually goes to evil.com! ❌ Bypass!
Why This Happens
- yarl: Interprets
httpbin.org\as the userinfo component, and@as the userinfo/host separator, so the URL is parsed asuser=httpbin.org\,host=evil.com,path=/ - urllib3: URL-encodes the backslash as
%5C, so\@evil.com/becomes/%5C@evil.com/which is treated as part of the path, leavinghost=httpbin.org
This inconsistency allows an attacker to: - Bypass the hostname allowlist check - Access arbitrary internal/external services - Perform full SSRF attacks
Fixes
- https://github.com/vllm-project/vllm/pull/34743
Severity ?
5.4 (Medium)
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "vllm"
},
"ranges": [
{
"events": [
{
"introduced": "0.15.1"
},
{
"fixed": "0.17.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-25960"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-09T19:55:32Z",
"nvd_published_at": "2026-03-09T21:16:15Z",
"severity": "MODERATE"
},
"details": "## Summary\n\nThe SSRF protection fix for https://github.com/vllm-project/vllm/security/advisories/GHSA-qh4c-xf7m-gxfc can be bypassed in the `load_from_url_async` method due to inconsistent URL parsing behavior between the validation layer and the actual HTTP client.\n\n## Affected Component\n\n- **File**: `vllm/connections.py`\n- **Function**: `load_from_url_async`\n\n## Vulnerability Details\n\n### Root Cause\n\nThe SSRF [fix](https://github.com/vllm-project/vllm/pull/32746) uses `urllib3.util.parse_url()` to validate and extract the hostname from user-provided URLs. However, `load_from_url_async` uses `aiohttp` for making the actual HTTP requests, and `aiohttp` internally uses the `yarl` library for URL parsing.\n\nThese two URL parsers handle backslash characters (`\\`) differently:\n\n| Parser | Input URL | Parsed Host | Parsed Path | Behavior |\n|--------|-----------|-------------|-------------|----------|\n| `urllib3.parse_url()` | `https://httpbin.org\\@evil.com/` | `httpbin.org` | `/%5C@evil.com/` | URL-encodes `\\` as `%5C`, treats `\\@evil.com/` as part of the path |\n| `yarl` (via aiohttp) | `https://httpbin.org\\@evil.com/` | `evil.com` | `/` | Treats `\\` as part of userinfo (`user: httpbin.org\\`), the `@` acts as the userinfo/host separator |\n\n### Attack Scenario\n\n```python\n# Attacker provides this URL\nmalicious_url = \"https://httpbin.org\\\\@evil.com/\"\n\n# 1. Validation layer (urllib3.parse_url)\nparsed = urllib3.util.parse_url(malicious_url)\n# parsed.host == \"httpbin.org\" \u2705 Passes validation\n\n# 2. Actual request (aiohttp with yarl)\nasync with aiohttp.ClientSession() as session:\n async with session.get(malicious_url) as response:\n # Request actually goes to evil.com! \u274c Bypass!\n```\n\n### Why This Happens\n\n1. **yarl**: Interprets `httpbin.org\\` as the userinfo component, and `@` as the userinfo/host separator, so the URL is parsed as `user=httpbin.org\\`, `host=evil.com`, `path=/`\n2. **urllib3**: URL-encodes the backslash as `%5C`, so `\\@evil.com/` becomes `/%5C@evil.com/` which is treated as part of the path, leaving `host=httpbin.org`\n\nThis inconsistency allows an attacker to:\n- Bypass the hostname allowlist check\n- Access arbitrary internal/external services\n- Perform full SSRF attacks\n\n## Fixes\n\n- https://github.com/vllm-project/vllm/pull/34743",
"id": "GHSA-v359-jj2v-j536",
"modified": "2026-03-10T18:39:20Z",
"published": "2026-03-09T19:55:32Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/vllm-project/vllm/security/advisories/GHSA-qh4c-xf7m-gxfc"
},
{
"type": "WEB",
"url": "https://github.com/vllm-project/vllm/security/advisories/GHSA-v359-jj2v-j536"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25960"
},
{
"type": "WEB",
"url": "https://github.com/vllm-project/vllm/pull/34743"
},
{
"type": "WEB",
"url": "https://github.com/vllm-project/vllm/commit/6f3b2047abd4a748e3db4a68543f8221358002c0"
},
{
"type": "PACKAGE",
"url": "https://github.com/vllm-project/vllm"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L",
"type": "CVSS_V3"
}
],
"summary": "vLLM has SSRF Protection Bypass"
}
Loading…
Loading…
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Loading…
Loading…