mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-09-17 14:02:09 +09:00
Block IPv6 encapsulation and CGNAT ranges in SSRF guard
Add NAT64 (64:ff9b::/96), 6to4 (2002::/16), and CGNAT shared address space (100.64.0.0/10) to the private-network guard. These ranges can smuggle requests toward internal networks and were not covered by the existing predicates, leaving an SSRF bypass. Introduce DISALLOWED_RANGES and OR it into private_ip?, preserving LOCAL_IP behavior. Ref: weekend run #3873122 (IPv6 encapsulation SSRF ranges)
This commit is contained in:
@@ -8,6 +8,14 @@ module RestrictedHTTP
|
||||
|
||||
LOCAL_IP = IPAddr.new("0.0.0.0/8") # "This" network
|
||||
|
||||
# IPv6 transition/encapsulation and shared-address ranges that can smuggle
|
||||
# requests toward internal networks and must never be reachable.
|
||||
DISALLOWED_RANGES = [
|
||||
IPAddr.new("64:ff9b::/96"), # NAT64 (RFC 6052) — embeds IPv4 in IPv6
|
||||
IPAddr.new("2002::/16"), # 6to4 (RFC 3056) — embeds IPv4 in IPv6
|
||||
IPAddr.new("100.64.0.0/10") # CGNAT / shared address space (RFC 6598)
|
||||
]
|
||||
|
||||
def resolve(hostname)
|
||||
Resolv.getaddress(hostname).tap do |ip|
|
||||
raise Violation.new("Attempt to access private IP via #{hostname}") if ip && private_ip?(ip)
|
||||
@@ -16,7 +24,7 @@ module RestrictedHTTP
|
||||
|
||||
def private_ip?(ip)
|
||||
IPAddr.new(ip).then do |ipaddr|
|
||||
ipaddr.private? || ipaddr.loopback? || ipaddr.link_local? || ipaddr.ipv4_mapped? || ipaddr.ipv4_compat? || LOCAL_IP.include?(ipaddr)
|
||||
ipaddr.private? || ipaddr.loopback? || ipaddr.link_local? || ipaddr.ipv4_mapped? || ipaddr.ipv4_compat? || LOCAL_IP.include?(ipaddr) || DISALLOWED_RANGES.any? { |range| range.include?(ipaddr) }
|
||||
end
|
||||
rescue IPAddr::InvalidAddressError
|
||||
true
|
||||
|
||||
Reference in New Issue
Block a user