fix(ssrf): block 6to4 and NAT64 IPv6 wrappers of private IPv4#13
Merged
Conversation
isBlockedIpv6 didn't decode two IPv6 transition forms that embed an IPv4
address, so an attacker could smuggle a private/reserved/metadata target past
the v6 check:
- 6to4 (2002::/16): embedded v4 in hextets 1-2, e.g. 2002:a9fe:a9fe:: →
169.254.169.254 (cloud metadata), 2002:7f00:1:: → 127.0.0.1.
- NAT64 well-known prefix (64:ff9b::/96): embedded v4 in the low 32 bits,
e.g. 64:ff9b::7f00:1 → 127.0.0.1.
Both previously fell through to "allowed".
Decode the embedded IPv4 and range-check it via isBlockedIpv4 — consistent
with the existing ::ffff: IPv4-mapped handling, so a 6to4/NAT64 wrapper of a
PUBLIC v4 (e.g. 2002:808:808::) stays allowed while a wrapper of a
private/reserved v4 is blocked. A comment documents the residual NAT64
network-specific-prefix limitation (not generically decodable).
The DNS-error fail-open in assertResolvesToPublic is deliberately unchanged.
Tests: 6to4 + NAT64 wrappers of loopback/metadata/private (hex and dotted
forms) now blocked; public wrappers still allowed; bracketed IP-literal hosts
rejected by validateOutboundUrl. Full suite 1263 pass, lint clean, build green.
Review: adversarial 2-lens (correctness false-neg/pos + completeness of other
embedding vectors) — 0 real problems; Teredo/ISATAP/6bone confirmed not
reachable from a server fetch with no tunnel interface (deferred as hygiene).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The SSRF guard's
isBlockedIpv6didn't decode two IPv6 transition forms that embed an IPv4 address, so an attacker could smuggle a private/reserved/metadata target past the v6 check:2002::/16, v4 in hextets 1–2)2002:a9fe:a9fe::169.254.169.254(cloud metadata)2002:7f00:1::127.0.0.164:ff9b::/96, v4 in low 32 bits)64:ff9b::7f00:1127.0.0.1Both previously fell through to allowed.
Change
Decode the embedded IPv4 and range-check it via
isBlockedIpv4— consistent with the existing::ffff:IPv4-mapped handling, so a 6to4/NAT64 wrapper of a public v4 (e.g.2002:808:808::→ 8.8.8.8) stays allowed while a wrapper of a private/reserved v4 is blocked. A code comment documents the residual NAT64 network-specific-prefix limitation (an operator-chosen prefix can't be decoded generically; the pre-fetch DNS re-resolution is the backstop for hostnames).The DNS-error fail-open in
assertResolvesToPublicis deliberately unchanged (per decision).Tests
6to4 + NAT64 wrappers of loopback/metadata/private (hex and dotted forms) now blocked; public wrappers still allowed; bracketed IP-literal hosts rejected by
validateOutboundUrl. 1263 pass, lint clean, build green.Review
Adversarial 2-lens (correctness false-neg/pos + completeness of other embedding vectors): 0 real problems — the reviewer executed the decode logic against 16 candidate addresses to confirm no bypass. Teredo / ISATAP / 6bone confirmed not reachable from a server-side fetch with no tunnel interface (deferred as hygiene); ISATAP's link-local form is already blocked.
🤖 Generated with Claude Code