GitHub Advisoryadvisory · npm
GHSA-cmhj-wh2f-9cgx 9router: Image prefetch DNS rebinding allows SSRF to internal services
HIGH
Official page ↗
- Aliases
- CVE-2026-56676
- Ecosystem
- npm
- Packages
- 9router
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L
- Weaknesses
- CWE-367, CWE-918
- Reviewed
- 2026-09-23
Affected packages
• npm 9router: < 0.5.2 → fixed in 0.5.2
Description
Summary
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).
Details
• Affected version / commit: 9router `v0.4.80` @ `b282f05`.
• Reachable through `/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.
• The provider used in this reproduction is the bundled mock provider — no real API
key and no real provider call.
• internal-admin (the SSRF target) is not exposed to the host network; it is
reachable only from inside the Docker network.
• rebind-dns behaviour for `rebind.9r.test`:
• first A response → `1.1.1.1` (public) to pass the public-host guard,
• second A response → `172.29.0.10` (internal-admin) during the fetch.
• internal-admin logs `GET /ssrf-marker` with `peer=172.29.0.30` (the `proxied-router`
container), proving the server-side fetch landed on the internal service.
• mock-provider receives `POST /api/chat` and the flow completes with `HTTP 200`.
• Root cause: DNS TOCTOU — the IP is not pinned between the validation resolution
(the public-host guard) and the fetch resolution. The guard and the fetch each resolve the
hostname independently, so a TTL-0 rebinding authority can return a public IP to the guard
and an internal IP to the fetch.
Proof of Concept
This repository is a self-contained Docker Compose reproduction. No real provider is called
and no real API key is required.
1. Build and start the stack:
```bash
docker compose up --build
```
2. Confirm `internal-admin` is unreachable from the host:
```bash
curl -i http://127.0.0.1:18083/ssrf-marker # connection refused / fail
docker compose ps # internal-admin has NO host port mapping
```
3. Send the request named `POST image-prefetch DNS rebinding trigger` from
`requests.http`, or with curl:
```bash
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
}'
```
Impact
• SSRF to internal HTTP services reachable from the 9router host/container.
• Depending on the environment, this can reach cloud metadata endpoints, internal admin
panels, or be used for internal service discovery.
• Blind / semi-blind SSRF when the fetched response is not returned to the attacker; an
exfil variant (pointing the image at an internal endpoint that returns valid image bytes)
can return internal content base64-encoded to the upstream.
• Requires a code path that prefetches/normalizes remote images for vision-capable
providers.
• No real credential is needed for the reproduction.
Suggested Fix
• Pin the resolved IP after validation and connect to that IP (resolve once, then
reuse the address for the fetch).
• Block private, loopback, link-local, multicast, and cloud-metadata ranges at connect
time, not only at validation time.
• Perform DNS resolution and IP checks immediately before the request and against the
address actually used to connect.
• Disable redirects, or re-validate every redirect target with the same checks.
• Enforce an allowlist for image-fetch domains where feasible.
• Add a timeout, a response size limit, and a content-type check.
Related
Alias · CVE (1)
- CVE-2026-56676 9Router is an AI router & token saver. vulnerability
References
- https://github.com/decolua/9router/security/advisories/GHSA-cmhj-wh2f-9cgx web
- https://nvd.nist.gov/vuln/detail/CVE-2026-56676 advisory
- https://github.com/decolua/9router/commit/c7d07448c58bec1200741de0b73305b860416b82 web
- https://github.com/decolua/9router package
- https://github.com/decolua/9router/releases/tag/v0.5.2 web