Additional IP range validations (#1152)

* [bugfix] Ensure requests happen over TCP

It's possible for the network to be udp4 or udp6. This is rather
unlikely to occur, but since we're given the network anyway as part of
the Sanitize function getting called we might as well check for it.

* [chore] Align reserved v6 blocks to IANA registry

* [chore] Add test for ValidateIP

The net and netip packages diverge in that net.ParseIP will consider an
IPv4-mapped address to be an IPv4 address and as such it would get
caught by the IPv4Reserved list. However, netip considers it an IPv6
address, so we need to ensure the mapped range is in IPv6Reserved.

* [chore] Align reserved v4 blocks to IANA registry

This includes a number of tests for /32's explicitly called out in the
registry to ensure we always consider those invalid.
This commit is contained in:
Daniele Sluijters 2022-11-26 12:09:55 +01:00 committed by GitHub
parent e6cd81babc
commit 746f3fa4e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 89 additions and 6 deletions

View file

@ -36,6 +36,9 @@ import (
// ErrInvalidRequest is returned if a given HTTP request is invalid and cannot be performed.
var ErrInvalidRequest = errors.New("invalid http request")
// ErrInvalidNetwork is returned if the request would not be performed over TCP
var ErrInvalidNetwork = errors.New("invalid network type")
// ErrReservedAddr is returned if a dialed address resolves to an IP within a blocked or reserved net.
var ErrReservedAddr = errors.New("dial within blocked / reserved IP range")

View file

@ -38,6 +38,10 @@ func (s *sanitizer) Sanitize(ntwrk, addr string, _ syscall.RawConn) error {
return err
}
if !(ntwrk == "tcp4" || ntwrk == "tcp6") {
return ErrInvalidNetwork
}
// Seperate the IP
ip := ipport.Addr()

View file

@ -24,16 +24,34 @@ import (
var (
// IPv6Reserved contains IPv6 reserved IP prefixes.
// https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
IPv6Reserved = [...]netip.Prefix{
netip.MustParsePrefix("::1/128"), // Loopback
netip.MustParsePrefix("fe80::/10"), // Link-local
netip.MustParsePrefix("fc00::/7"), // Unique Local
netip.MustParsePrefix("2001:db8::/32"), // Test, doc, examples
netip.MustParsePrefix("ff00::/8"), // Multicast
netip.MustParsePrefix("fec0::/10"), // Site-local, deprecated
netip.MustParsePrefix("::1/128"), // Loopback
netip.MustParsePrefix("::/128"), // Unspecified address
netip.MustParsePrefix("::ffff:0:0/96"), // IPv4-mapped address
netip.MustParsePrefix("64:ff9b::/96"), // IPv4/IPv6 translation, RFC 6052
netip.MustParsePrefix("64:ff9b:1::/48"), // IPv4/IPv6 translation, RFC 8215
netip.MustParsePrefix("100::/64"), // Discard prefix, RFC 6666
netip.MustParsePrefix("2001::/23"), // IETF Protocol Assignments, RFC 2928
netip.MustParsePrefix("2001::/32"), // Teredo
netip.MustParsePrefix("2001:1::1/128"), // Port Control Protocol Anycast, RFC 7723
netip.MustParsePrefix("2001:1::2/128"), // Traversal Using Relays around NAT Anycast, RFC 8155
netip.MustParsePrefix("2001:2::/48"), // Benchmarking, RFC 5180
netip.MustParsePrefix("2001:3::/32"), // AMT, RFC 7450
netip.MustParsePrefix("2001:4:112::/48"), // AS112-v6, RFC 7535
netip.MustParsePrefix("2001:10::/28"), // ORCHID, deprecated
netip.MustParsePrefix("2001:20::/28"), // ORCHIDv2
netip.MustParsePrefix("2001:db8::/32"), // Test, doc, examples
netip.MustParsePrefix("2002::/16"), // 6to4
netip.MustParsePrefix("2620:4f:8000::/48"), // Direct Delegation AS112 Service, RFC 7534
netip.MustParsePrefix("fc00::/7"), // Unique Local
netip.MustParsePrefix("fe80::/10"), // Link-local
netip.MustParsePrefix("fec0::/10"), // Site-local, deprecated
netip.MustParsePrefix("ff00::/8"), // Multicast
}
// IPv4Reserved contains IPv4 reserved IP prefixes.
// https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
IPv4Reserved = [...]netip.Prefix{
netip.MustParsePrefix("0.0.0.0/8"), // Current network
netip.MustParsePrefix("10.0.0.0/8"), // Private
@ -42,9 +60,13 @@ var (
netip.MustParsePrefix("169.254.0.0/16"), // Link-local
netip.MustParsePrefix("172.16.0.0/12"), // Private
netip.MustParsePrefix("192.0.0.0/24"), // RFC6890
netip.MustParsePrefix("192.0.0.0/29"), // IPv4 Service Continuity Prefix, RFC 7335
netip.MustParsePrefix("192.0.2.0/24"), // Test, doc, examples
netip.MustParsePrefix("192.31.196.0/24"), // AS112-v4, RFC 7535
netip.MustParsePrefix("192.52.193.0/24"), // AMT, RFC 7450
netip.MustParsePrefix("192.88.99.0/24"), // IPv6 to IPv4 relay
netip.MustParsePrefix("192.168.0.0/16"), // Private
netip.MustParsePrefix("192.175.48.0/24"), // Direct Delegation AS112 Service, RFC 7534
netip.MustParsePrefix("198.18.0.0/15"), // Benchmarking tests
netip.MustParsePrefix("198.51.100.0/24"), // Test, doc, examples
netip.MustParsePrefix("203.0.113.0/24"), // Test, doc, examples

View file

@ -0,0 +1,54 @@
package netutil
import (
"net/netip"
"testing"
)
func TestValidateIP(t *testing.T) {
tests := []struct {
name string
ip netip.Addr
}{
// IPv4 tests
{
name: "IPv4 this host on this network",
ip: netip.MustParseAddr("0.0.0.0"),
},
{
name: "IPv4 dummy address",
ip: netip.MustParseAddr("192.0.0.8"),
},
{
name: "IPv4 Port Control Protocol Anycast",
ip: netip.MustParseAddr("192.0.0.9"),
},
{
name: "IPv4 Traversal Using Relays around NAT Anycast",
ip: netip.MustParseAddr("192.0.0.10"),
},
{
name: "IPv4 NAT64/DNS64 Discovery 1",
ip: netip.MustParseAddr("192.0.0.17"),
},
{
name: "IPv4 NAT64/DNS64 Discovery 2",
ip: netip.MustParseAddr("192.0.0.171"),
},
// IPv6 tests
{
name: "IPv4-mapped address",
ip: netip.MustParseAddr("::ffff:169.254.169.254"),
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if valid := ValidateIP(tc.ip); valid != false {
t.Fatalf("Expected IP %s to be: %t, got: %t", tc.ip, false, valid)
}
})
}
}