Skip to content

Block NAT64 and IPv4-compatible embedded addresses in load_web_page SSRF guard - #6486

Closed
herdiyana256 wants to merge 5 commits into
google:mainfrom
herdiyana256:fix-load-web-page-nat64-ssrf
Closed

herdiyana256 wants to merge 5 commits into
google:mainfrom
herdiyana256:fix-load-web-page-nat64-ssrf

Conversation

@herdiyana256

Copy link
Copy Markdown
Contributor

load_web_page validates fetch targets through _is_blocked_address, which rejects any address that is not global. That correctly blocks IPv4 private, loopback, and link-local literals, hostnames that resolve to them, and the IPv4-mapped IPv6 form ::ffff:a.b.c.d.

It does not block two other IPv6 encodings that carry an internal IPv4 address. The NAT64 well-known prefix 64:ff9b::/96 (RFC 6052) and the deprecated IPv4-compatible form ::a.b.c.d both embed an IPv4 address in their low 32 bits, but the standard library classifies them as globally routable, so is_global is True and the guard allows them. As a result 64:ff9b::169.254.169.254 and ::169.254.169.254 pass the check, and the same holds for private and loopback ranges.

On a NAT64-enabled network (common in IPv6-only environments) a connection to 64:ff9b::169.254.169.254 is translated by the NAT64 gateway to the IPv4 link-local metadata endpoint 169.254.169.254, so an attacker who can influence the URL passed to the tool can reach cloud metadata or other internal services despite the guard. The target can also be delivered through DNS by publishing an AAAA record inside the prefix.

This change unwraps the embedded IPv4 address from the IPv4-mapped, NAT64 well-known, and IPv4-compatible forms and re-checks it, so a target is blocked when either the IPv6 address or its embedded IPv4 address is non-global. NAT64 to a genuinely public IPv4 such as 64:ff9b::8.8.8.8 is still allowed, and the existing load_web_page test suite continues to pass.

@adk-bot adk-bot added the tools [Component] This issue is related to tools label Jul 26, 2026
…SRF guard

_is_blocked_address rejected any address that is not global, which correctly
blocks IPv4 private/loopback/link-local literals and their IPv4-mapped IPv6
form. It did not block the NAT64 well-known prefix (64:ff9b::/96) or the
deprecated IPv4-compatible form (::a.b.c.d): ipaddress classifies both as
globally routable even though they embed an internal IPv4 address. On a
NAT64-enabled network a request to 64:ff9b::169.254.169.254 is translated to
the IPv4 link-local metadata endpoint, so the guard could be bypassed to reach
internal or metadata addresses.

Unwrap the embedded IPv4 address from these forms and re-check it, so a target
is blocked when either the IPv6 address or its embedded IPv4 address is
non-global. NAT64 to a genuinely public IPv4 (for example 64:ff9b::8.8.8.8)
is still allowed.
@rohityan
rohityan force-pushed the fix-load-web-page-nat64-ssrf branch from 602a5c7 to 4cc8fed Compare July 27, 2026 21:24
@rohityan rohityan added the request clarification [Status] The maintainer need clarification or more information from the author label Jul 27, 2026
@rohityan

Copy link
Copy Markdown
Collaborator

Hi @herdiyana256 , Thank you for your contribution! We appreciate you taking the time to submit this pull request.
Can you also add a unit test and fix the failing CI checks.

@herdiyana256

Copy link
Copy Markdown
Contributor Author

Thanks @rohityan! Added unit tests in tests/unittests/tools/test_load_web_page.py:

  • test_is_blocked_address_blocks_embedded_internal_ipv4 / test_is_blocked_address_allows_embedded_public_ipv4 — parametrized over the NAT64 well-known prefix (64:ff9b::/96), the deprecated IPv4-compatible form (::a.b.c.d) and the IPv4-mapped form: blocked when the embedded IPv4 is internal (loopback / 169.254.169.254 / private), allowed when it is public.
  • End-to-end load_web_page cases for a NAT64-wrapped metadata literal, an IPv4-compatible loopback literal, and a hostname resolving (via getaddrinfo) to a NAT64-wrapped 169.254.169.254 — all confirm the request is refused before any socket send.

All pass locally (pytest tests/unittests/tools/test_load_web_page.py), CLA/checks green. PTAL.

@herdiyana256

Copy link
Copy Markdown
Contributor Author

Merged main in to clear the out-of-date branch state; it merged cleanly with no conflicts. pytest tests/unittests/tools/test_load_web_page.py is green (21 passed) on the updated tree, including the NAT64 / IPv4-compatible / IPv4-mapped coverage added earlier.

@xuanyang15 this one is assigned to you and the review comments from @rohityan (unit tests + CI) are already addressed. Would you mind taking a look when you have a moment?

@xuanyang15

Copy link
Copy Markdown
Collaborator

@herdiyana256 Thanks for creating this PR! This change appears to be a duplicate of earlier PR #6347, which addresses the same NAT64 and IPv4-compatible SSRF vulnerability in load_web_page and was submitted on 2026-07-08 (18 days prior to PR #6486).

We will proceed with #6347 to credit the earlier PR.

@xuanyang15 xuanyang15 closed this Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

request clarification [Status] The maintainer need clarification or more information from the author tools [Component] This issue is related to tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants