Jul 29, 2026·6 min read·31 visits
A Time-of-Check to Time-of-Use (TOCTOU) DNS vulnerability in datamodel-code-generator allows remote attackers to bypass SSRF protections using DNS rebinding or IPv6-mapped IPv4 addresses, exposing internal networks.
CVE-2026-55391 is a server-side request forgery (SSRF) bypass vulnerability in the datamodel-code-generator package. The vulnerability occurs due to a Time-of-Check to Time-of-Use (TOCTOU) race condition during DNS resolution, combined with a failure to inspect embedded IPv4-in-IPv6 address mappings. By deploying a malicious DNS server with a low Time-To-Live (TTL) configuration, an attacker can bypass private address blocklists and coerce the application to connect to internal services or local endpoints.
The datamodel-code-generator package is a Python library used to generate structural structures, including Pydantic models, TypedDicts, and dataclasses, from remote schema definitions. To retrieve these definitions, the application supports fetching remote URLs when specified in the command-line interface or parsed as references within schemas. When the library is instructed to exclude private networks, a validation routine inspects the remote domain to confirm it resolves to a global, publicly routable IP address.
This implementation contains a fundamental vulnerability due to a Time-of-Check to Time-of-Use (TOCTOU) gap during network resolution. The library validates the target host during an initial DNS resolution phase but then relies on the underlying HTTP client library, which executes a second, independent DNS resolution during the socket connection phase. Because these phases are disconnected, a remote attacker can execute a DNS rebinding attack to bypass private network restrictions.
A secondary flaw also exists in how the validation engine processes IPv6-mapped IPv4 addresses. Specially constructed IPv6 structures mapping to local or private loops bypass the initial routability checks and allow connections to restricted endpoints.
The underlying vulnerability manifests in the get_body() function within src/datamodel_code_generator/http.py. When a target schema is fetched, the library employs a validation step to ensure that requested URLs resolve to global, public IP addresses before opening an HTTP connection. This security mechanism is intended to protect the host from Server-Side Request Forgery.
However, a severe Time-of-Check to Time-of-Use (TOCTOU) gap occurs. The validation step resolves the DNS domain name to verify its IP address using a standard socket call. Once validated, the library hands the raw string URL to the httpx HTTP client. The HTTP client then performs its own distinct DNS lookup to establish the actual network socket connection.
Because the DNS resolution is not pinned or cached between these two steps, an attacker can control the authoritative DNS server for the targeted domain. By specifying a Time-To-Live (TTL) of zero seconds, the attacker can return a safe, public IP during validation and a local or private IP during the connection phase. This forces the HTTP client to connect to local interfaces despite validation passing.
Additionally, the validation step relies on ip.is_global to flag unauthorized local IP addresses. However, IPv6 architectures allow embedding IPv4 targets inside IPv6 structures, such as mapped addresses (::ffff:127.0.0.1), compatible addresses (::127.0.0.1), or NAT64-mapped addresses. The operating system unrolls these to private targets, while standard library validation erroneously classifies the outer IPv6 shell as global.
To understand the technical mechanics, consider the vulnerable logic flow. The application validated host targets by fetching IP arrays from the _get_ips_from_host function and verifying each IP using _is_safe_ip. This resolved DNS independently of the transport engine:
def _get_ips_from_host(host: str) -> tuple[IPv4Address | IPv6Address, ...]:
# ...
addr_infos = socket.getaddrinfo(host, None, ...)
# ...The fundamental issue was that after validating these IP instances, the library called httpx.get(current_url, ...) directly. It did not restrict httpx to the exact IP addresses that had just passed validation. This let httpx query DNS again during socket creation.
To remediate this, the developer implemented a custom transport backend that pins DNS resolution. The fixed transport uses the _PinnedNetworkBackend class to intercept connection attempts and enforce the use of pre-validated IP structures:
class _PinnedNetworkBackend:
def __init__(
self,
*,
pinned_host: str,
pinned_ips: tuple[IPv4Address | IPv6Address, ...],
backend: _NetworkBackend,
) -> None:
self._pinned_host = _normalize_dns_host(pinned_host)
self._pinned_ips = pinned_ips
self._backend = backend
def connect_tcp(
self,
host: str,
port: int,
# ...
) -> httpcore.NetworkStream:
if _normalize_dns_host(host) != self._pinned_host:
msg = f"Requested DNS host {host} does not match the validated host"
raise OSError(msg)
last_error: Exception | None = None
for ip in self._pinned_ips:
try:
# Forces connection directly to the pre-validated IP address
return self._backend.connect_tcp(
str(ip),
port,
timeout=timeout,
local_address=local_address,
socket_options=socket_options,
)
except Exception as exc:
last_error = excThis architecture forces the connection process to use the explicitly validated IPs, eliminating the TOCTOU gap entirely. Additionally, manual redirection loops were introduced so that any redirected targets must pass validation and pinning loops before access.
To initiate exploitation, an attacker configures an authoritative DNS server to manage a domain name under their control, such as rebound.attacker.com. The DNS server is programmed to return two different sets of IP addresses depending on the sequence or timing of incoming requests.
On the first query, which corresponds to the validation phase of datamodel-code-generator, the DNS server returns a public IP address. Because this public IP resolves to a globally routable resource, the validation checks succeed.
On the second query, which corresponds to the socket connection phase, the DNS server returns an internal IP address, such as 127.0.0.1 or the AWS link-local metadata address 169.254.169.254. Since the connection is initiated immediately after validation, the HTTP client connects directly to the internal endpoint.
By leveraging this sequence, an attacker can access sensitive configuration frameworks, metadata services, and databases running on loopback networks.
The impact of a successful SSRF attack via DNS rebinding is significant. If datamodel-code-generator is integrated into an enterprise microservice or an automated processing pipeline, the application could be used to extract highly sensitive structural data.
In cloud environments, this allow access to internal metadata APIs like the AWS/GCP Instance Metadata Service (IMDS). Attackers can request sensitive IAM credentials and session tokens, leading to potential cloud control plane compromise.
In local cluster deployments, the exploit can target internal resources that rely solely on network boundaries for security. Services that do not require authentication on localhost are especially vulnerable to manipulation and data exfiltration.
To address this vulnerability, security administrators must upgrade datamodel-code-generator to version 0.63.0 or later. This version contains the socket pinning and address unwrapping mitigations.
If upgrading is not immediately possible, apply network-level controls. Configure outbound firewalls to block all traffic originating from the code-generation containers destined for internal subnets and metadata IP addresses.
Additionally, validation processes can be hardened by ensuring that remote references are fetched through an explicit gateway proxy. This proxy should perform its own strict DNS caching and enforce robust request boundaries.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:L/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
datamodel-code-generator koxudaxi | >= 0.50.0, < 0.63.0 | 0.63.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-918 (SSRF), CWE-367 (TOCTOU) |
| Attack Vector | Network |
| Attack Complexity | High |
| CVSS v3.1 Score | 7.5 |
| Exploit Status | Proof-of-Concept |
| CISA KEV Status | Not Listed |
The web application receives a URL or similar identifier from an upstream source and retrieves the contents at that URL, but it does not sufficiently ensure that the request is being sent to the intended destination.
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.
Contao Open Source CMS versions 4.0.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 contain a Cross-Site Request Forgery (CSRF) vulnerability in backend parameter handling. The `RequestTokenListener` component validates anti-CSRF tokens solely for HTTP POST requests, while GET-based declarative guards run only when an `act` parameter is present in the query string. Consequently, custom backend actions dispatched via alternative parameters such as `key=` can execute without CSRF token verification when triggered by an authenticated user.
Contao CMS versions 4.1.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 fail to validate form submission tokens and enforce rate limiting when processing activation email resend requests via HTTP POST, enabling resource exhaustion and account state enumeration.
An information disclosure vulnerability in Contao CMS allows unauthenticated site visitors to view protected page titles, URLs, and text excerpts through search queries when protected page indexing is disabled after previously being enabled.
In Vikunja prior to version 2.6.0, relation creation via the CalDAV endpoint fails to invoke the TaskRelation.CanCreate authorization check. This missing access control allows an authenticated user to establish unauthorized relationships and perform write operations against any task, provided its unique identifier (UID) is known.
A cross-project information disclosure vulnerability in Vikunja allows authenticated users with read access to one project to view private task details from unauthorized projects via subtask expansion parameters.