GitHub Advisoryadvisory · npm

GHSA-cmhj-wh2f-9cgx 9router: Image prefetch DNS rebinding allows SSRF to internal services

HIGH Published 2026-09-23 Modified 2026-09-23 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)

Weakness · CWE (2)

  • CWE-367 Time-of-check Time-of-use (TOCTOU) Race Condition weakness
  • CWE-918 Server-Side Request Forgery weakness

References