mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 00:22:14 +01:00
fix(gotenberg): block IPv6 prefixes that tunnel to internal IPv4 in IsPublicIP
This commit is contained in:
@@ -45,11 +45,45 @@ var outboundDialer = &net.Dialer{
|
|||||||
KeepAlive: 30 * time.Second,
|
KeepAlive: 30 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nonPublicIPv6Prefixes lists IPv6 ranges that the standard library does
|
||||||
|
// not classify via [netip.Addr] helpers but that must not be considered
|
||||||
|
// public:
|
||||||
|
//
|
||||||
|
// - 2002::/16 6to4 (RFC 3056, deprecated by RFC 7526). Bits 16-47
|
||||||
|
// embed an IPv4 destination, including private ones.
|
||||||
|
// - 2001::/32 Teredo (RFC 4380). Bits 96-127 embed an IPv4
|
||||||
|
// destination, including private ones.
|
||||||
|
// - 64:ff9b::/96 NAT64 well-known prefix (RFC 6052). Low 32 bits
|
||||||
|
// embed an IPv4 destination translated by a NAT64 gateway.
|
||||||
|
// - 64:ff9b:1::/48 NAT64 local-use prefix (RFC 8215). Same risk.
|
||||||
|
// - fec0::/10 Deprecated site-local (RFC 3879). Not covered by
|
||||||
|
// [netip.Addr.IsPrivate] which only handles fc00::/7.
|
||||||
|
// - ::/96 IPv4-compatible IPv6 (deprecated). Embeds an IPv4
|
||||||
|
// destination and is not handled by [netip.Addr.Unmap].
|
||||||
|
// - 2001:db8::/32 Documentation range (RFC 3849). Never routable.
|
||||||
|
// - 100::/64 Discard prefix (RFC 6666).
|
||||||
|
var nonPublicIPv6Prefixes = []netip.Prefix{
|
||||||
|
netip.MustParsePrefix("2002::/16"),
|
||||||
|
netip.MustParsePrefix("2001::/32"),
|
||||||
|
netip.MustParsePrefix("64:ff9b::/96"),
|
||||||
|
netip.MustParsePrefix("64:ff9b:1::/48"),
|
||||||
|
netip.MustParsePrefix("fec0::/10"),
|
||||||
|
netip.MustParsePrefix("::/96"),
|
||||||
|
netip.MustParsePrefix("2001:db8::/32"),
|
||||||
|
netip.MustParsePrefix("100::/64"),
|
||||||
|
}
|
||||||
|
|
||||||
// IsPublicIP reports whether addr is reachable on the public internet. It
|
// IsPublicIP reports whether addr is reachable on the public internet. It
|
||||||
// returns false for loopback, private (RFC1918), link-local, unspecified,
|
// returns false for loopback, private (RFC1918), link-local, unspecified,
|
||||||
// multicast, and unique-local addresses. IPv4-mapped IPv6 addresses are
|
// multicast, and unique-local addresses. IPv4-mapped IPv6 addresses are
|
||||||
// unmapped before evaluation so that [::ffff:127.0.0.1] is correctly
|
// unmapped before evaluation so that [::ffff:127.0.0.1] is correctly
|
||||||
// identified as loopback.
|
// identified as loopback.
|
||||||
|
//
|
||||||
|
// IPv6 prefixes that tunnel or translate to an embedded IPv4 destination
|
||||||
|
// (6to4, Teredo, NAT64) are rejected wholesale rather than recursed into,
|
||||||
|
// because a host that routes them implicitly trusts the IPv4 mapping and
|
||||||
|
// the prefixes themselves are deprecated or translation-only. See
|
||||||
|
// [nonPublicIPv6Prefixes] for the full list and rationale.
|
||||||
func IsPublicIP(addr netip.Addr) bool {
|
func IsPublicIP(addr netip.Addr) bool {
|
||||||
if !addr.IsValid() {
|
if !addr.IsValid() {
|
||||||
return false
|
return false
|
||||||
@@ -65,6 +99,13 @@ func IsPublicIP(addr netip.Addr) bool {
|
|||||||
addr.IsInterfaceLocalMulticast():
|
addr.IsInterfaceLocalMulticast():
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if addr.Is6() {
|
||||||
|
for _, p := range nonPublicIPv6Prefixes {
|
||||||
|
if p.Contains(addr) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,38 @@ func TestIsPublicIP(t *testing.T) {
|
|||||||
// Multicast.
|
// Multicast.
|
||||||
{"224.0.0.1", false},
|
{"224.0.0.1", false},
|
||||||
{"ff02::1", false},
|
{"ff02::1", false},
|
||||||
|
|
||||||
|
// 6to4 wrapping internal/private IPv4 (RFC 3056, deprecated by
|
||||||
|
// RFC 7526). a9fe:a9fe = 169.254.169.254 (cloud metadata).
|
||||||
|
{"2002:a9fe:a9fe::", false},
|
||||||
|
{"2002:0a00:0001::", false},
|
||||||
|
{"2002:c0a8:0101::", false},
|
||||||
|
|
||||||
|
// 6to4 wrapping a public IPv4 (8.8.8.8) is rejected wholesale.
|
||||||
|
{"2002:0808:0808::", false},
|
||||||
|
|
||||||
|
// NAT64 well-known prefix (RFC 6052).
|
||||||
|
{"64:ff9b::a9fe:a9fe", false},
|
||||||
|
{"64:ff9b::0808:0808", false},
|
||||||
|
|
||||||
|
// NAT64 local-use prefix (RFC 8215).
|
||||||
|
{"64:ff9b:1::a9fe:a9fe", false},
|
||||||
|
|
||||||
|
// Teredo (RFC 4380).
|
||||||
|
{"2001:0:abcd:ef12:3456:7890:a9fe:a9fe", false},
|
||||||
|
|
||||||
|
// Deprecated site-local (RFC 3879).
|
||||||
|
{"fec0::1", false},
|
||||||
|
{"feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", false},
|
||||||
|
|
||||||
|
// IPv4-compatible IPv6 (deprecated, not handled by Unmap).
|
||||||
|
{"::a9fe:a9fe", false},
|
||||||
|
|
||||||
|
// Documentation prefix (RFC 3849).
|
||||||
|
{"2001:db8::1", false},
|
||||||
|
|
||||||
|
// Discard prefix (RFC 6666).
|
||||||
|
{"100::1", false},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.addr, func(t *testing.T) {
|
t.Run(tc.addr, func(t *testing.T) {
|
||||||
addr, err := netip.ParseAddr(tc.addr)
|
addr, err := netip.ParseAddr(tc.addr)
|
||||||
|
|||||||
Reference in New Issue
Block a user