You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[BUG] Server-side request forgery via multimodal image_url fetching: internal read channel and status-code probing oracle #1608
On multimodal deployments, LightLLM fetches client-supplied image_url and audio_url values on the server side with no scheme allowlist and no private address filtering. Any client that can call the public inference API can make the server issue HTTP GET requests to internal addresses. The fetched content is fed into the vision model, so an attacker can read internal image resources by asking the model to describe them, and fetch errors with status codes are returned to the client, which gives a port and path probing oracle. Both directions were reproduced: a loopback-only resource was read out by the model, and 404, closed port and open-but-not-image targets returned three distinct error bodies. Suggested severity High, CVSS 3.1 AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N (7.6), CWE-918.
Details
The OpenAI-compatible chat endpoint accepts standard vision content parts (lightllm/server/api_openai.py): a part of type image_url with an http:// or https:// URL becomes an ImageItem of url type. In lightllm/server/multimodal_params.py, ImageItem.preload() (and AudioItem on the same pattern) calls fetch_resource() for url items.
fetch_resource() in lightllm/utils/multimodal_utils.py does the fetch:
The URL is used as supplied. There is no host allowlist, no private or link-local address filtering, and no response origin check. The timeout defaults to 5 seconds (REQUEST_TIMEOUT) and responses up to about 128 MB are accepted. Redirects are not followed.
Two impact paths follow from this:
Read channel. On success the fetched bytes become the image that the vision model processes. A request like "transcribe the text in the image" returns the content of an internal image resource through the model output.
Probing oracle. On failure ImageItem.preload() raises ValueError(f"Failed to read image type=url, data[:100]={url}: {e}"), and both /v1/chat/completions and the native /generate return that message to the client as a 400 body. The embedded exception distinguishes an HTTP error status (Client error '404 File not found'), a closed port (All connection attempts failed!), and an open port that does not return an image (cannot identify image file).
Verified at commit 1eb4810c (v1.2.0+50); the same code is present at tag v1.2.0 and on current main (checked 2026-09-30).
PoC
Tested on Linux, LightLLM at commit 1eb4810c78c6ea908b69c20c3c037b79979e1cd6, Qwen2.5-VL-3B-Instruct, normal run mode on one GPU, streaming chat working.
On the victim host, start a loopback-only HTTP server with a probe image (an external host must not be able to reach it):
From a second host, confirm the probe is unreachable directly (connection refused), then ask the public API to read it:
importrequestsr=requests.post("http://<node_ip>:8091/v1/chat/completions", json={
"model": "qwen",
"messages": [{"role": "user", "content": [
{"type": "image_url", "image_url": {"url": "http://127.0.0.1:17777/probe.png"}},
{"type": "text", "text": "transcribe the text in the image"},
]}],
"stream": True}, timeout=60)
print("".join(lforlinr.text.split('"content":"') ifTrue) orr.text)
Expected result: HTTP 200 and the stream contains SSRF-PROBE-7f3a9c, which the server wrote after fetching a resource that only the server itself could reach. The node log shows Begin to download resource from url: http://127.0.0.1:17777/probe.png. The same works with the URL pointing at an attacker-controlled host, which is the exfiltration direction for internal services that return images.
For the oracle, send three more requests with these URLs in the same content part and record the 400 bodies:
Each internal state maps to a distinct error body, on both /v1/chat/completions and /generate. All results reproduced across three cold starts of the node, and the read channel also worked when the API call itself came from a second host. Expected correct behavior: validate the URL against a configured allowlist, block private and link-local ranges, and do not return internal fetch error details to the client.
Impact
Server-side request forgery (CWE-918). The attacker only needs to reach the public inference API of any multimodal deployment; no internal network access is required, and no non-default option is involved. Confidentiality: the server fetches internal URLs on behalf of the attacker, the model discloses the content of internal resources that are returned as images, and the error channel discloses internal port and path state. Integrity and availability are not affected by this issue. The code path is not covered by any previously issued LightLLM advisory.
Activity