SSRF: block NAT64 and 6to4 IPv6 encapsulation ranges#2987
Conversation
DISALLOWED_IP_RANGES covered the NAT64 well-known prefix (RFC6052, 64:ff9b::/96) and 6to4 (RFC3056, 2002::/16) — both encapsulate IPv4 destinations and can be used to smuggle otherwise-blocked targets past SSRF filtering. Add both ranges and cover them with blocked_address? assertions plus a public control. Weekend run #3873122.
There was a problem hiding this comment.
Pull request overview
This PR hardens SsrfProtection against SSRF bypasses via IPv6 encapsulation schemes by expanding the disallowed IP ranges to include NAT64 and 6to4 prefixes, with tests covering the newly blocked address forms.
Changes:
- Add NAT64 well-known prefix (
64:ff9b::/96) and 6to4 (2002::/16) toSsrfProtection::DISALLOWED_IP_RANGES. - Add unit tests asserting these encapsulated IPv6 addresses are blocked (and a public control is allowed) via
blocked_address?.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| app/models/ssrf_protection.rb | Extends the SSRF denylist to cover NAT64 and 6to4 IPv6 encapsulation ranges. |
| test/models/ssrf_protection_test.rb | Adds coverage to ensure the new encapsulation ranges are blocked (plus a public allow control). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| IPAddr.new("198.18.0.0/15") # Benchmark testing (RFC2544) | ||
| IPAddr.new("198.18.0.0/15"), # Benchmark testing (RFC2544) | ||
| IPAddr.new("64:ff9b::/96"), # NAT64 well-known prefix (RFC6052) | ||
| IPAddr.new("2002::/16") # 6to4 (RFC3056) |
|
🤖 Closing in deference to #2988 ( One gap to carry over — noted on #2988 directly: the well-known-prefix check keys only on |
|
Both follow-ups from this PR are now handled on #2988 (95b2d43): the RFC 8215 local-use NAT64 prefix On the Copilot trailing-comma nit: the cited constants ( |
What
SsrfProtection'sDISALLOWED_IP_RANGEScovered CGNAT (100.64.0.0/10) and benchmark (198.18.0.0/15) but missed the IPv6 encapsulation ranges NAT6464:ff9b::/96and 6to42002::/16. These embed an IPv4 address (including private space) in an IPv6 address thatprivate?/link_local?do not catch.Found via weekend run #3873122 (once-campfire OpenGraph-unfurl SSRF); confirmed latent across bc3/fizzy/once-campfire.
Fix
Add NAT64 and 6to4 to
DISALLOWED_IP_RANGES.blocked_address?already applies to every resolved address; the DNS resolution path (resolve_public_ip, which already reads all A/AAAA records) is unchanged.Tests
test/models/ssrf_protection_test.rb:blocked_address?is true for64:ff9b::a00:1and2002::1, false for a public control (93.184.216.34).bin/rails test test/models/ssrf_protection_test.rb→ 19 tests, 0 failures.