{"id":"CVE-2026-56676","aliases":["EUVD-2026-42929","GHSA-cmhj-wh2f-9cgx"],"status":"published","title":"9router: Image prefetch DNS rebinding allows SSRF to internal services","description":"9Router is an AI router \u0026 token saver. Prior to 0.5.2, 9router validates image URLs by resolving the host before fetching, but open-sse/translator/concerns/image.js performs the later server-side image fetch with a separate DNS resolution. An authenticated attacker with access to the LLM proxy can use a vision-capable model and an attacker-controlled DNS name that first resolves to a public IP and then rebinds to an internal address, allowing server-side requests to internal-only HTTP services. This issue is fixed in version 0.5.2.","severity":{"version":"3.1","score":7.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.0019,"percentile":0.08693,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-367","CWE-918"],"published":"2026-07-10T15:30:49Z","modified":"2026-10-28T02:30:42Z","fixes":[{"ecosystem":"npm","package":"9router","fixed_in":["0.5.2"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"9router","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"0.4.80"},{"fixed":"0.5.2"}]}]},{"source":"osv","ecosystem":"npm","package":"9router","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.5.2"}]}]}],"products":[{"vendor":"decolua","product":"9router"}],"references":[{"url":"https://github.com/decolua/9router/security/advisories/GHSA-cmhj-wh2f-9cgx","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/decolua/9router/commit/c7d07448c58bec1200741de0b73305b860416b82","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/decolua/9router/releases/tag/v0.5.2","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-56676","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-cmhj-wh2f-9cgx"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/56xxx/CVE-2026-56676.json","tags":["advisory"]},{"url":"https://github.com/decolua/9router","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-56676","title":"9router: Image prefetch DNS rebinding allows SSRF to internal services","description":"9Router is an AI router \u0026 token saver. Prior to 0.5.2, 9router validates image URLs by resolving the host before fetching, but open-sse/translator/concerns/image.js performs the later server-side image fetch with a separate DNS resolution. An authenticated attacker with access to the LLM proxy can use a vision-capable model and an attacker-controlled DNS name that first resolves to a public IP and then rebinds to an internal address, allowing server-side requests to internal-only HTTP services. This issue is fixed in version 0.5.2.","status":"published","published":"2026-07-10T15:30:49.514Z","modified":"2026-07-13T18:11:54.297Z","cvss":[{"version":"3.1","score":7.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L","level":"high","assessor":"cna"}],"cwes":["CWE-367","CWE-918"],"updated_at":"2026-11-02T13:37:02.780526Z"}],"euvd":[{"id":"EUVD-2026-42929","description":"9Router is an AI router \u0026 token saver. Prior to 0.5.2, 9router validates image URLs by resolving the host before fetching, but open-sse/translator/concerns/image.js performs the later server-side image fetch with a separate DNS resolution. An authenticated attacker with access to the LLM proxy can use a vision-capable model and an attacker-controlled DNS name that first resolves to a public IP and then rebinds to an internal address, allowing server-side requests to internal-only HTTP services. This issue is fixed in version 0.5.2.","status":"published","published":"2026-07-10T15:30:49Z","modified":"2026-07-13T18:11:54Z","cvss":[{"version":"3.1","score":7.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L","level":"high"}],"updated_at":"2026-11-02T18:20:01.134466Z"}],"ghsa":[{"id":"GHSA-cmhj-wh2f-9cgx","title":"9router: Image prefetch DNS rebinding allows SSRF to internal services","description":"## Summary\n\n9router validates image URLs by resolving the host before fetching, but the later\nserver-side fetch performs a separate DNS resolution. An attacker-controlled DNS name can\nresolve to a public IP during validation and then rebind to an internal Docker/private IP\nduring the fetch. This allows the server-side image prefetch to reach internal-only HTTP\nservices (SSRF).\n\n## Details\n\n- **Affected version / commit:** 9router `v0.4.80` @ `b282f05`.\n- **Reachable through** `/v1/chat/completions` with a **vision-capable model** and an\n  `image_url` content part. A vision-capable model name is required so the image survives\n  modality stripping and the server-side prefetch is armed.\n- The provider used in this reproduction is the bundled **mock provider** — **no real API\n  key and no real provider call**.\n- **internal-admin** (the SSRF target) is **not exposed to the host network**; it is\n  reachable only from inside the Docker network.\n- **rebind-dns** behaviour for `rebind.9r.test`:\n  - first A response → `1.1.1.1` (public) to pass the public-host guard,\n  - second A response → `172.29.0.10` (internal-admin) during the fetch.\n- **internal-admin** logs `GET /ssrf-marker` with `peer=172.29.0.30` (the `proxied-router`\n  container), proving the server-side fetch landed on the internal service.\n- **mock-provider** receives `POST /api/chat` and the flow completes with `HTTP 200`.\n- **Root cause:** DNS TOCTOU — the IP is **not pinned** between the validation resolution\n  (the public-host guard) and the fetch resolution. The guard and the fetch each resolve the\n  hostname independently, so a TTL-0 rebinding authority can return a public IP to the guard\n  and an internal IP to the fetch.\n\n## Proof of Concept\n\nThis repository is a self-contained Docker Compose reproduction. No real provider is called\nand no real API key is required.\n\n1. Build and start the stack:\n   ```bash\n   docker compose up --build\n   ```\n2. Confirm `internal-admin` is unreachable from the host:\n   ```bash\n   curl -i http://127.0.0.1:18083/ssrf-marker   # connection refused / fail\n   docker compose ps                            # internal-admin has NO host port mapping\n   ```\n3. Send the request named **`POST image-prefetch DNS rebinding trigger`** from\n   [`requests.http`](./requests.http), or with curl:\n   ```bash\n   curl -i -X POST http://127.0.0.1:18082/v1/chat/completions \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\n       \"model\": \"ollama-local/gemma3\",\n       \"messages\": [{\"role\":\"user\",\"content\":[\n         {\"type\":\"text\",\"text\":\"reproduction image-prefetch trigger\"},\n         {\"type\":\"image_url\",\"image_url\":{\"url\":\"http://rebind.9r.test:8080/ssrf-marker?case=rebind-trigger\"}}\n       ]}],\n       \"stream\": false\n     }'\n   ```\n\n## Impact\n\n- SSRF to internal HTTP services reachable from the 9router host/container.\n- Depending on the environment, this can reach cloud metadata endpoints, internal admin\n  panels, or be used for internal service discovery.\n- Blind / semi-blind SSRF when the fetched response is not returned to the attacker; an\n  exfil variant (pointing the image at an internal endpoint that returns valid image bytes)\n  can return internal content base64-encoded to the upstream.\n- Requires a code path that prefetches/normalizes remote images for vision-capable\n  providers.\n- No real credential is needed for the reproduction.\n\n## Suggested Fix\n\n- **Pin the resolved IP** after validation and connect to **that** IP (resolve once, then\n  reuse the address for the fetch).\n- Block private, loopback, link-local, multicast, and cloud-metadata ranges **at connect\n  time**, not only at validation time.\n- Perform DNS resolution and IP checks immediately before the request and against the\n  address actually used to connect.\n- Disable redirects, or re-validate every redirect target with the same checks.\n- Enforce an allowlist for image-fetch domains where feasible.\n- Add a timeout, a response size limit, and a content-type check.","status":"published","published":"2026-09-23T18:12:28Z","modified":"2026-09-23T18:12:29Z","cvss":[{"version":"3.1","score":7.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L","level":"high"}],"cwes":["CWE-367","CWE-918"],"updated_at":"2026-11-02T14:41:20.575159Z"}],"nvd":[{"id":"CVE-2026-56676","description":"9Router is an AI router \u0026 token saver. Prior to 0.5.2, 9router validates image URLs by resolving the host before fetching, but open-sse/translator/concerns/image.js performs the later server-side image fetch with a separate DNS resolution. An authenticated attacker with access to the LLM proxy can use a vision-capable model and an attacker-controlled DNS name that first resolves to a public IP and then rebinds to an internal address, allowing server-side requests to internal-only HTTP services. This issue is fixed in version 0.5.2.","status":"published","published":"2026-07-10T16:16:34.923Z","modified":"2026-10-25T15:10:00.147Z","cvss":[{"version":"3.1","score":7.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L","level":"high","assessor":"secondary"}],"cwes":["CWE-367","CWE-918"],"updated_at":"2026-11-02T14:12:28.771746Z"}],"osv":[{"id":"CVE-2026-56676","title":"9router: Image prefetch DNS rebinding allows SSRF to internal services","description":"9Router is an AI router \u0026 token saver. Prior to 0.5.2, 9router validates image URLs by resolving the host before fetching, but open-sse/translator/concerns/image.js performs the later server-side image fetch with a separate DNS resolution. An authenticated attacker with access to the LLM proxy can use a vision-capable model and an attacker-controlled DNS name that first resolves to a public IP and then rebinds to an internal address, allowing server-side requests to internal-only HTTP services. This issue is fixed in version 0.5.2.","status":"published","published":"2026-07-10T15:30:49.514Z","modified":"2026-10-28T02:30:42.434920426Z","cvss":[{"version":"3.1","score":7.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L","level":"high"}],"cwes":["CWE-367","CWE-918"],"updated_at":"2026-11-02T15:45:11.609219Z"},{"id":"GHSA-cmhj-wh2f-9cgx","title":"9router: Image prefetch DNS rebinding allows SSRF to internal services","description":"## Summary\n\n9router validates image URLs by resolving the host before fetching, but the later\nserver-side fetch performs a separate DNS resolution. An attacker-controlled DNS name can\nresolve to a public IP during validation and then rebind to an internal Docker/private IP\nduring the fetch. This allows the server-side image prefetch to reach internal-only HTTP\nservices (SSRF).\n\n## Details\n\n- **Affected version / commit:** 9router `v0.4.80` @ `b282f05`.\n- **Reachable through** `/v1/chat/completions` with a **vision-capable model** and an\n  `image_url` content part. A vision-capable model name is required so the image survives\n  modality stripping and the server-side prefetch is armed.\n- The provider used in this reproduction is the bundled **mock provider** — **no real API\n  key and no real provider call**.\n- **internal-admin** (the SSRF target) is **not exposed to the host network**; it is\n  reachable only from inside the Docker network.\n- **rebind-dns** behaviour for `rebind.9r.test`:\n  - first A response → `1.1.1.1` (public) to pass the public-host guard,\n  - second A response → `172.29.0.10` (internal-admin) during the fetch.\n- **internal-admin** logs `GET /ssrf-marker` with `peer=172.29.0.30` (the `proxied-router`\n  container), proving the server-side fetch landed on the internal service.\n- **mock-provider** receives `POST /api/chat` and the flow completes with `HTTP 200`.\n- **Root cause:** DNS TOCTOU — the IP is **not pinned** between the validation resolution\n  (the public-host guard) and the fetch resolution. The guard and the fetch each resolve the\n  hostname independently, so a TTL-0 rebinding authority can return a public IP to the guard\n  and an internal IP to the fetch.\n\n## Proof of Concept\n\nThis repository is a self-contained Docker Compose reproduction. No real provider is called\nand no real API key is required.\n\n1. Build and start the stack:\n   ```bash\n   docker compose up --build\n   ```\n2. Confirm `internal-admin` is unreachable from the host:\n   ```bash\n   curl -i http://127.0.0.1:18083/ssrf-marker   # connection refused / fail\n   docker compose ps                            # internal-admin has NO host port mapping\n   ```\n3. Send the request named **`POST image-prefetch DNS rebinding trigger`** from\n   [`requests.http`](./requests.http), or with curl:\n   ```bash\n   curl -i -X POST http://127.0.0.1:18082/v1/chat/completions \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\n       \"model\": \"ollama-local/gemma3\",\n       \"messages\": [{\"role\":\"user\",\"content\":[\n         {\"type\":\"text\",\"text\":\"reproduction image-prefetch trigger\"},\n         {\"type\":\"image_url\",\"image_url\":{\"url\":\"http://rebind.9r.test:8080/ssrf-marker?case=rebind-trigger\"}}\n       ]}],\n       \"stream\": false\n     }'\n   ```\n\n## Impact\n\n- SSRF to internal HTTP services reachable from the 9router host/container.\n- Depending on the environment, this can reach cloud metadata endpoints, internal admin\n  panels, or be used for internal service discovery.\n- Blind / semi-blind SSRF when the fetched response is not returned to the attacker; an\n  exfil variant (pointing the image at an internal endpoint that returns valid image bytes)\n  can return internal content base64-encoded to the upstream.\n- Requires a code path that prefetches/normalizes remote images for vision-capable\n  providers.\n- No real credential is needed for the reproduction.\n\n## Suggested Fix\n\n- **Pin the resolved IP** after validation and connect to **that** IP (resolve once, then\n  reuse the address for the fetch).\n- Block private, loopback, link-local, multicast, and cloud-metadata ranges **at connect\n  time**, not only at validation time.\n- Perform DNS resolution and IP checks immediately before the request and against the\n  address actually used to connect.\n- Disable redirects, or re-validate every redirect target with the same checks.\n- Enforce an allowlist for image-fetch domains where feasible.\n- Add a timeout, a response size limit, and a content-type check.","status":"published","published":"2026-09-23T18:12:28Z","modified":"2026-09-23T18:27:56.218933611Z","cvss":[{"version":"3.1","score":7.4,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L","level":"high"}],"cwes":["CWE-367","CWE-918"],"updated_at":"2026-11-02T15:46:45.28161Z"}]}}
