Skip to content

fix: block SSRF via IPv6 addresses embedding a non-global IPv4 (NAT64/IPv4-compatible)#6347

Open
adilburaksen wants to merge 3 commits into
google:mainfrom
adilburaksen:security/ssrf-embedded-ipv4-block
Open

fix: block SSRF via IPv6 addresses embedding a non-global IPv4 (NAT64/IPv4-compatible)#6347
adilburaksen wants to merge 3 commits into
google:mainfrom
adilburaksen:security/ssrf-embedded-ipv4-block

Conversation

@adilburaksen

Copy link
Copy Markdown
Contributor

Summary

load_web_page vets every resolved address with _is_blocked_address, which currently rejects an address only when not address.is_global. For IPv6, is_global on the outer address does not reflect the reachability of an embedded IPv4 target:

Address is_global Embedded IPv4
64:ff9b::169.254.169.254 (NAT64, RFC 6052) True 169.254.169.254
::169.254.169.254 (IPv4-compatible) True 169.254.169.254

On a network with NAT64 (common in IPv6-only / DNS64 clusters, e.g. GKE), a URL such as
http://[64:ff9b::169.254.169.254]/computeMetadata/v1/ passes the is_global check and is
routed by NAT64 to the internal 169.254.169.254 metadata endpoint — an SSRF that can reach the
cloud metadata server / other link-local and private ranges.

The existing IPv4-mapped (::ffff:a.b.c.d) and 6to4 (2002::/16) forms are already reported as
non-global by ipaddress, so they were handled; NAT64 and the deprecated IPv4-compatible form are
the gaps.

Fix

Add _embedded_ipv4() which extracts the embedded IPv4 for IPv4-mapped, 6to4, NAT64
(64:ff9b::/96) and IPv4-compatible addresses, and reject the outer address when the embedded
IPv4 is not globally routable. Public NAT64 targets (e.g. 64:ff9b::8.8.8.8) remain allowed, so
there is no over-blocking of legitimate destinations.

Scope / honesty

Reaching the internal target requires the deployment network to actually route the embedded form
(NAT64 present, or a stack that routes IPv4-compatible IPv6). Where it doesn't route, this is
defense-in-depth completing the existing SSRF filter; where NAT64 is present it closes a real
metadata-SSRF path. Added regression tests for both forms.

load_web_page vets resolved addresses with `not address.is_global`, but
`is_global` on an IPv6 address does not reflect the embedded IPv4 target
for NAT64 (`64:ff9b::/96`) and IPv4-compatible (`::a.b.c.d`) addresses.
On a network with NAT64 (e.g. IPv6-only / DNS64 clusters), a URL like
`http://[64:ff9b::169.254.169.254]/` is treated as global and reaches the
internal 169.254.169.254 metadata endpoint. Extract the embedded IPv4 and
reject it when it is not globally routable.
@adilburaksen

Copy link
Copy Markdown
Contributor Author

Note: the same not address.is_global vetting (which this PR refines to also decode embedded IPv4) is being introduced for RestApiTool in #6003, so it will have the same NAT64/IPv4-compatible blind spot. It may be worth factoring _embedded_ipv4 (or the whole address-vetting routine) into a shared helper so both load_web_page and RestApiTool benefit from a single, complete implementation.

@rohityan rohityan self-assigned this Jul 9, 2026
@rohityan rohityan added the tools [Component] This issue is related to tools label Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tools [Component] This issue is related to tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants