[php-src] PR #25426: ext/filter: Fix FILTER_VALIDATE_IP range flag bypasses through IPv6 notations

From: Date: Thu, 12 Nov 2026 16:24:10 +0000
Subject: [php-src] PR #25426: ext/filter: Fix FILTER_VALIDATE_IP range flag bypasses through IPv6 notations
Groups: php.git-pulls 
Request: Send a blank email to git-pulls+get-40588@lists.php.net to get a copy of this message
Pull Request: https://github.com/php/php-src/pull/25426 Author: xavierleune Target: PHP-8.4 FILTER_FLAG_NO_PRIV_RANGE, FILTER_FLAG_NO_RES_RANGE and FILTER_FLAG_GLOBAL_RANGE are commonly used to keep user-supplied addresses away from internal networks (SSRF), but several IPv6 notations of internal addresses pass them. These are "Low" issues under the security policy, hence fixed as regular bugs. Almost all changes make the flags reject more addresses, which cannot open a bypass. ```php filter_var('fc00::1.2.3.4', FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE); // passes (fc00::102:304 does not) filter_var('::ffff:10.0.0.1', FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE); // passes, reaches 10.0.0.1 from a dual-stack socket filter_var('64:ff9b::a9fe:a9fe', FILTER_VALIDATE_IP, FILTER_FLAG_GLOBAL_RANGE); // passes, reaches 169.254.169.254 through NAT64 filter_var('fec0::1', FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE); // passes, site-local ``` ## Commits 1. **Dotted IPv4 suffix.** The parser stored every X::a.b.c.d as ::ffff:a.b.c.d, discarding the hexadecimal prefix. Blocks are now stored at their real index; validation itself is unchanged. ::/96 is marked reserved so that ::a.b.c.d stays rejected, and only 64:ff9b::/96 is global, not the whole /32. 2. **Embedded IPv4 addresses.** The flags now look at the embedded IPv4 address: - NAT64 64:ff9b::/96: classified as the IPv4 address itself (RFC 6052 §3.1). - IPv4-mapped, IPv4-compatible and IPv4-translated (::ffff:0:0:0/96): reserved, and private if the IPv4 address is. - 6to4 2002::/16: still non-global, private or reserved if the IPv4 address is. - 64:ff9b:1::/48 (RFC 8215): private, since the IPv4 position depends on the operator. - Teredo is unchanged: its client address is obfuscated and public by design. 3. **Missing blocks.** | Block | RFC | Classification | |---|---|---| | fec0::/10 site-local (deprecated, still used internally) | 3879 | private | | ff00::/8, 224.0.0.0/4 multicast | 4291, 5771 | reserved | | 3fff::/20 documentation | 9637 | not global | | 5f00::/16 SRv6 SIDs | 9602 | not global | ## Behaviour changes origin/PHP-8.4 → this branch. ✅ accepted, ❌ rejected. | Address | GLOBAL_RANGE | NO_RES_RANGE | NO_PRIV_RANGE | |---|---|---|---| | fc00::1.2.3.4 | ❌ | ❌ → ✅ | ✅ → ❌ | | 2606:4700::8.8.8.8, 64:ff9b::8.8.8.8 | ❌ → ✅ | ❌ → ✅ | ✅ | | 2001:db8::1.2.3.4 | ❌ | ❌ → ✅ | ✅ | | 64:ff9b::10.0.0.1 | ❌ | ❌ → ✅ | ✅ → ❌ | | 64:ff9b::a00:1 | ✅ → ❌ | ✅ | ✅ → ❌ | | 64:ff9b::a9fe:a9fe, 64:ff9b::7f00:1 | ✅ → ❌ | ✅ → ❌ | ✅ | | 64:ff9b:1::a00:1 | ✅ → ❌ | ✅ | ✅ → ❌ | | 2002:a00:1:: (6to4 of 10.0.0.1) | ❌ | ✅ | ✅ → ❌ | | 2002:7f00:1:: (6to4 of 127.0.0.1) | ❌ | ✅ → ❌ | ✅ | | ::ffff:10.0.0.1, ::10.0.0.1, ::ffff:0:10.0.0.1 | ❌ | ❌ | ✅ → ❌ | | ::7f00:1, ::808:808, ::ffff:0:7f00:1 | ✅ → ❌ | ✅ → ❌ | ✅ | | 1::127.0.0.1, ::1:127.0.0.1 | ❌ → ✅ | ❌ → ✅ | ✅ | | fec0::1 | ✅ → ❌ | ✅ | ✅ → ❌ | | ff02::1, 224.0.0.1 | ✅ → ❌ | ✅ → ❌ | ✅ | | 3fff::1, 5f00::1 | ✅ → ❌ | ✅ | ✅ | Addresses that become accepted are either dotted forms now classified like their hexadecimal form, which already passed, or actually global addresses. None leads to an internal network. CGNAT (100.64.0.0/10) is intentionally unchanged. ## Notes - bug61700.phpt expected ::ffff:192.168.1.1 to pass NO_PRIV_RANGE, the opposite of what #61700 reported and requinix confirmed. GH-7476 was a refactoring that, per its description, "does not fix what we consider as reserved and private, respectively, but merely tries to keep what we had so far". This PR completes #61700. - bug77221.phpt: 64:ff9b:: and 64:ff9b::ffff embed 0.0.0.0/8 and are no longer global; two really global addresses were added. filter_ipv4_rfc6890.phpt: 224.0.0.0/4 gets its own case. - Checked outside the tests: validation is identical on 400,000 random inputs, and 163,502 dotted addresses classify like their inet_pton() hex form (298,181 mismatches before). - No UPGRADING on 8.4 (NEWS only); a note will go to master's UPGRADING on merge-up.

« previous php.git-pulls (#40588) next »