- CVE-2026-91768: PHP-FPM compared only 12 of 16 bytes of an IPv6 client address, so one allowed entry admitted a whole /96 block.
- It matters where the allow list is the only barrier to the FastCGI endpoint, because the bypass can mean running PHP scripts.
- Check IPv6 TCP pools running any build older than the 24 September 2026 releases. PHP 8.1 and older get no patch. Test that outside clients are refused.
A lock that opens for the wrong key is hard to catch. Everyone with the right key still gets in, so nothing looks broken. CVE-2026-91768 in PHP is a lock of this kind. It shows that a security rule can be wrong for years while every normal test passes, because normal tests only check who gets in, not who is kept out.
What the flaw is
PHP-FPM is the process manager that runs PHP code for many web servers. It talks to the web server over a protocol called FastCGI. The setting listen.allowed_clients is a list of addresses allowed to connect. The PHP Group's advisory says the IPv6 branch of that check looks at only 12 bytes. A full IPv6 address is 16 bytes long, so a quarter of it goes unread.
In effect, each entry on the list matches a whole block of addresses that share the same first 96 bits. Security teams call that block a /96. The function responsible is fcgi_is_allowed() in main/fastcgi.c.
Alexandre Daubois, who wrote the fix, explains how it happened. The code compares memory with memcmp, and the IPv4 branch correctly asks for 4 bytes. The IPv6 branch asks for 12. That number is right in the branch just below it, where it is an offset for skipping the ::ffff: prefix of IPv4-mapped addresses. In the IPv6 branch it was used as a length.
What it let through
The advisory describes a test against a real php-fpm, not a mock. A request sent from ::1 to [::1]:19091 ran a test script, even though the list did not contain ::1. The two addresses share their first 96 bits. In a control test the list held 2001:db8::2, and the same request was refused, because the first 96 bits differed.
Daubois puts the scale in plain numbers. A pool configured with ::1 accepted connections from every address in ::/96.
He adds a second case. An entry written as ::ffff:127.0.0.1 is stored as an IPv6 entry. On a dual-stack listener, every IPv4 client then matched it, so one allowed IPv4 address meant all of them.
Why it lasted so long
Daubois traces the IPv6 code to a commit by Rémi Collet on 17 November 2014. That commit reached users first in PHP 5.5.20 and 5.6.4. He says the comparison line then stayed exactly as written for about eleven years. Neither source describes attacks in the wild.
His explanation is the lesson here. The test suite had no coverage of the IPv6 allow list. A working configuration looks the same whether the match is exact or a /96. The bug shows itself only when someone connects from a client that ought to be turned away. Daubois points out that few teams run that kind of probe against a system already serving live traffic.
This shows a wider habit. Teams confirm that access rules let the right people in, then stop. A rule that is too loose passes that test every time. Controls need a refusal test as well as an acceptance test.
Who is exposed
The advisory is specific. Exposure needs two things: FastCGI carried over IPv6 TCP, and an allow list treated as the real security boundary. The attacker needs network reachability and an address in the same /96 as an allowed client. In many deployments, reaching the endpoint means running PHP scripts in the worker.
Unix sockets, IPv4-only setups, and setups where an external firewall is the real boundary are not affected.
Daubois says every release from PHP 5.5.20 up to those shipping on 24 September 2026 is affected. He applied the patch to the PHP 8.2 branch first and ported it to 8.3, 8.4 and 8.5. PHP 8.1 and older will not get a patch. Builds on 8.2 and later are safe only once they run a release from 24 September or after.
He also says the fix adds no range support. Some teams may have used the loose match as an accidental way to admit a range. That stopped working with the 24 September releases, and nothing replaces it. He recommends handling ranges in a firewall or reverse proxy.
Questions for your team
Ask which PHP-FPM pools listen on IPv6 TCP, and which of them treat listen.allowed_clients as the only barrier. Ask which PHP versions run in production. Any 8.2 or later build older than the 24 September 2026 releases needs an update. Anything on 8.1 or older has no patch path. Ask whether any list entry was ::1 or an IPv4-mapped address.
Then ask for a refusal test. From an address that should be blocked, can anyone reach the endpoint? If the only protection is a list, put a network firewall in front of it as well.
A number that is correct in one place can be quietly wrong next to it. The same is true of an access rule that has only ever been tested with people who were meant to get in.
Produced by the WebPulse Newsroom with AI assistance from the original reporting credited below, and checked against that source by our editorial review. How we use AI.
Original reporting: The PHP Group (php-src project, via GitHub security advisory GHSA-62xp-839h-2637).





