DNS Rebinding Turns Name Validation into a Time-of-Use Risk
An outbound HTTP feature may appear safe after it rejects literal loopback and private IP addresses. The service parses a user-supplied URL, resolves its hostname, checks the returned address against an allowlist, then lets the HTTP client open the connection.
Those steps contain a gap. The policy decision applies to an address observed at one moment, while the network connection may perform another DNS lookup later. If the hostname is controlled by an attacker, its DNS answers can change between those events. The validated name stays identical while the destination address changes.
This pattern is commonly called DNS rebinding. In server-side URL fetching, it turns a hostname check into a time-of-check/time-of-use problem. A robust boundary validates the address that the socket will actually use, not merely an earlier answer for the same name.
A hostname is not a stable network destination
DNS maps names to records with cache lifetimes rather than assigning a permanent address to each hostname. Authoritative data can change, caches expire, and a name can legitimately return several addresses.
That flexibility is normal DNS behavior. It becomes security-relevant when an application treats a prior lookup as proof that a later connection will reach the same address.
Consider this sequence:
1. input: https://asset.example.invalid/file
2. validator resolves hostname
asset.example.invalid -> 203.0.113.40
3. policy accepts the address
4. HTTP client resolves hostname again
asset.example.invalid -> 127.0.0.1
5. client connects to 127.0.0.1The example addresses are illustrative. The defect is the split decision: one address was authorized and another address was used.
A short DNS TTL can make answer changes occur quickly, but TTL alone is not the security property. Even with caching, application components may use separate resolvers, separate caches, or different address-selection logic. The policy must not assume that two independent resolutions are identical.
URL parsing and DNS resolution are separate boundaries
A URL validator first has to establish what host the client will interpret. String checks are a poor substitute for a standards-aware URL parser because host syntax includes IPv4, IPv6, bracketed IPv6 literals, names, ports, user information, and normalization rules.
After parsing, literal IP addresses can be classified directly. Hostnames require resolution before an IP-based destination policy can be enforced.
A useful pipeline is:
raw URL
|
v
parse with one URL model
|
v
extract scheme, host, port
|
+---- IP literal ----> classify address
|
+---- hostname ------> resolve
|
v
classify every candidate
|
v
connect only to an accepted IPThe same parser semantics should feed both validation and connection setup. If validation interprets one host while the HTTP library interprets another, DNS pinning cannot repair the mismatch.
Classification has to cover the address space the policy intends to block
A rule that rejects only 127.0.0.1 is narrower than a rule that rejects loopback destinations. Likewise, rejecting RFC 1918 IPv4 ranges does not address IPv6 loopback, link-local addresses, or other ranges a deployment may consider internal or special-purpose.
The exact deny policy depends on the service. An internet-only fetcher often needs to exclude destinations that should not be reachable through untrusted input, including local, private, link-local, and deployment-specific infrastructure ranges. A service that intentionally reaches internal systems needs a different model, usually an explicit destination allowlist rather than a broad internet policy.
Classification should operate on parsed binary addresses where possible. Text forms can have multiple valid representations, especially for IPv6. Converting the address to the networking library’s canonical address type avoids building security policy around ad hoc string patterns.
IPv4-mapped IPv6 forms also deserve explicit treatment. The classifier should apply the intended IPv4 policy after normalizing such representations rather than allowing an alternate textual family to bypass the rule.
Every candidate address needs a policy decision
A DNS response can contain multiple A or AAAA records. Checking one accepted address and then handing the hostname back to a client is insufficient because the client may select another candidate.
For an internet-only policy, a conservative rule can reject the hostname if any candidate address falls outside the permitted destination set. Another design can retain only accepted candidates and constrain connection attempts to that filtered set. The correct choice depends on availability requirements, but the connection must not silently regain access to rejected candidates.
Address selection also changes over time. Happy Eyeballs-style connection logic may race IPv6 and IPv4 candidates. That is useful for latency, yet each candidate that can win the race has to satisfy the same destination policy.
The invariant is about possible connections, not the first record returned by a resolver.
Bind the approved address to the socket connection
The strongest fix removes the second unconstrained lookup. Resolve the hostname, apply policy to the resulting addresses, choose an accepted address, then connect the socket to that address directly.
The original hostname still matters for application protocols. For HTTPS, the TLS client normally needs the requested hostname for Server Name Indication (SNI) and certificate hostname verification. For HTTP, the request authority or Host field also represents the logical origin.
That produces two distinct values:
logical host: api.example.com
socket peer: 203.0.113.25The socket connects to the approved IP address. TLS verification still checks that the server certificate is valid for api.example.com, not for the numeric socket address, unless the application intentionally uses an IP-literal URL.
This separation preserves normal HTTPS identity checks while preventing the transport from resolving the hostname again outside the policy decision.
Redirects create a new destination decision
An HTTP redirect can move a request to a different scheme, hostname, port, or address. Validating only the initial URL leaves a second path around the outbound policy.
Each redirect target should pass through the same parsing, scheme, port, DNS, and address checks before another connection is opened. Automatic redirect handling is convenient, but a security-sensitive fetcher often needs a redirect hook or explicit redirect loop so policy runs before every hop.
A redirect can also point back to the same hostname after its DNS state changes. Reusing the original hostname approval is therefore not sufficient. Each new connection needs an address bound to the policy decision for that connection.
Redirect limits remain useful independently. They cap loops and bound work, but they do not replace destination validation.
DNS cache design does not replace connection binding
Pinning a hostname in an application cache for a fixed period can reduce repeated resolution, but cache duration is not a complete security boundary. Different processes may not share the cache, failures may trigger fresh resolution, and library behavior can bypass application-level state.
A local resolver cache also cannot prove that an HTTP client used the exact answer the validator inspected unless the connection path is tied to that result.
Caching can still be part of the design. It can reduce DNS traffic and provide stable address selection for a bounded interval. The security invariant remains simpler when expressed at the dial step: every socket peer must come from the set approved for that connection attempt.
Proxies move the enforcement point
If the application sends outbound requests through an HTTP or SOCKS proxy, the application may not perform the final DNS lookup or TCP connection itself. The proxy can receive a hostname and resolve it from another network context.
In that architecture, validating an address locally does not bind the proxy’s destination. The enforcement point has to move to the component that selects the actual peer, or the protocol between application and proxy must carry a destination form that preserves the approved address while retaining the required logical hostname metadata.
This is also relevant to service meshes and egress gateways. Centralized egress can be a strong control point, but only when its policy sees the destination information needed to enforce the same boundary.
Connection reuse needs the same invariant
HTTP connection pools complicate the picture in the opposite direction: a request may reuse an existing connection without performing DNS resolution at all.
Reuse is safe only if the pooled connection is associated with an origin and peer that the current policy permits. Policy changes, tenant-specific rules, or credentials with different network scopes can make a globally shared pool inappropriate.
The key question remains concrete: which peer will carry this request, and was that peer authorized under the policy that applies to this request?
Destination policy belongs at the dial boundary
DNS rebinding is difficult to contain with hostname syntax rules because the risk is temporal. A name can be acceptable during one lookup and point elsewhere during the next.
A durable outbound policy keeps URL parsing, DNS resolution, address classification, redirect handling, proxy behavior, and connection establishment in one security model. The decisive step is binding an approved address to the socket that carries the request.
That turns a fragile statement — “this hostname resolved to an allowed address earlier” — into a stronger invariant: “this connection can only open to an address that passed policy for this attempt.”
References
- RFC 1034, Domain Names - Concepts and Facilities: https://www.rfc-editor.org/rfc/rfc1034
- RFC 1035, Domain Names - Implementation and Specification: https://www.rfc-editor.org/rfc/rfc1035
- OWASP, Server-Side Request Forgery Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html