# datamodel-code-generator vulnerable to SSRF protection bypass via DNS rebinding

datamodel-code-generator validates a fetch URL's resolved IP once, then httpx re-resolves independently, so DNS rebinding reaches loopback and cloud metadata despite allow_private_network=False.

- Payout: Research
- Severity: high
- Program: datamodel-code-generator
- Found by: thegr1ffyn
- Disclosed: 2026-07-28
- CVE: CVE-2026-55391
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-vx7x-vcc2-c44g
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-vx7x-vcc2-c44g
- Tags: area:web domain:oss domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:python technique:race-window technique:source-review vuln:ssrf

**Target:** Python CLI/library HTTP schema fetcher (get_body in http.py)
**Root cause:** The anti-SSRF guard resolves the host via socket.getaddrinfo and checks the IPs, but the subsequent httpx.get performs its own independent DNS resolution, so the validated address is never pinned to the connection (TOCTOU).
**Steps:**
1. Attacker controls a low-TTL DNS name that first resolves to a public IP, then to a private one (e.g. 127.0.0.1 or 169.254.169.254).
2. Get the tool to fetch `http://that-host/schema.json` via `--url` or a remote `$ref` in a supplied schema (remote refs fetched by default).
3. `get_body()` calls `_validate_url_for_fetch()` -> `_get_ips_from_host()` (resolution #1) which sees the public IP and passes.
4. `httpx.get(current_url, ...)` (resolution #2) re-resolves and connects to the private IP, bypassing the guard.
5. Reproducer patches `socket.getaddrinfo` to return a public IP on the guard lookup and `127.0.0.1` on httpx's lookup against a loopback server.
**How found:** Code review of http.py: noticed validation resolves the host separately from the httpx connection, with nothing pinning the connection to the validated IP; redirect loop re-validates correctly, isolating the gap to the host-to-IP check.
**Exploitation:** Attacker registers a rebinding hostname and induces the tool to fetch it (via --url or remote $ref). The guard's resolution sees a safe public IP; httpx's connection resolution sees the private IP, reaching loopback, link-local metadata (169.254.169.254) or internal HTTP services. The fetched internal document is parsed and can be reflected into generated output, enabling metadata credential theft and internal port/host probing.
**Escalation:** Fetched internal response is parsed and reflected into generated output, turning blind SSRF into data exfiltration of internal/metadata content.
**Insight:** When auditing SSRF guards, check whether the validated IP is actually pinned to the socket/connection (custom transport, resolved-IP connect) or whether the HTTP client re-resolves; a correct redirect re-validation loop often coexists with an unpinned initial resolution.

