Refuse an IPv6 address that holds an internal IPv4 address (closes #297) #298
@@ -30,6 +30,14 @@ P2: security: per-origin rate limiting
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-10-16 P1 security bug: the check that keeps pixa from fetching internal
|
||||
addresses judges an IPv6 address in the IPv4-compatible form (`::/96`), such
|
||||
as `::127.0.0.1` or `::10.0.0.1`, by the IPv4 address in its last four bytes
|
||||
(closes #297). It read only the mapped form (`::ffff:a.b.c.d`) as IPv4, so a
|
||||
source host such as `[::127.0.0.1]` was checked as a public IPv6 address and
|
||||
fetched, which on a network that routes it reaches the internal IPv4 host; it
|
||||
is now refused with 403. The NAT64 form (`64:ff9b::/96`) was already refused
|
||||
as a whole, as one of the built-in blocked ranges, and still is.
|
||||
- 2026-10-16 a source host that is an IPv6 address in brackets with no port,
|
||||
such as `[2001:db8::a]`, is fetched like one with a port, through `/v1/image/`
|
||||
and through an encrypted URL (closes #295). The fetcher's check of the source
|
||||
|
||||
@@ -543,6 +543,13 @@ func isPrivateIP(ip net.IP) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// An IPv4-compatible IPv6 address (in ::/96, such as ::127.0.0.1) holds
|
||||
// an IPv4 address in its last four bytes and is judged as that address
|
||||
if addr, ok := netip.AddrFromSlice(ip); ok &&
|
||||
netip.MustParsePrefix("::/96").Contains(addr) {
|
||||
return isPrivateIP(ip[12:])
|
||||
}
|
||||
|
||||
// Check for loopback
|
||||
if ip.IsLoopback() {
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user