Описание
IPv6 ACL bypass in FastCGI listen.allowed_clients due to partial address comparison (memcmp 12 bytes)
Summary
The IPv6 branch of the FastCGI client access check compares only the first 12 bytes of a 16-byte IPv6 address, so listen.allowed_clients matches on a /96 prefix instead of the exact address. An attacker who can source an address sharing the first 96 bits with an allowed one passes the check and reaches the FastCGI endpoint.
Details
fcgi_is_allowed() in main/fastcgi.c compares IPv6 client addresses with a hard-coded length of 12:
https://github.com/php/php-src/blob/php-8.5.10/main/fastcgi.c#L1334
An exact IPv6 comparison must cover all 16 bytes of struct in6_addr. The IPv4 branch above it compares its full 4 bytes correctly, and the IN6_IS_ADDR_V4MAPPED branch legitimately uses a 12-byte offset to reach the embedded IPv4 address, but that offset is not a valid length for a plain IPv6 comparison. Allowed addresses are parsed as full addresses with inet_pton(AF_INET6, ...), so the truncated comparison silently widens every configured entry to a /96 network.
The fix compares sizeof(client_sa.sa_inet6.sin6_addr) bytes.
PoC
Tested against a real php-fpm (fpm-fcgi), not a mock.
- Configure a pool:
-
Run php-fpm and send a FastCGI request from
::1to[::1]:19091for a test script. -
The request is accepted and the script executes, even though
::1is not inlisten.allowed_clients. Both addresses share the first 96 bits. -
Control test: set
listen.allowed_clients = 2001:db8::2and send the same request from::1. The connection is denied as expected, because the first 96 bits differ.
Impact
This is an access control bypass in deployments that use FastCGI over IPv6 TCP and rely on listen.allowed_clients as a security boundary. An attacker with network reachability who can source an address within the same /96 as an allowed client bypasses the ACL and reaches the FastCGI endpoint, which in many deployments means executing PHP scripts in the FPM worker context.
Deployments using Unix sockets, IPv4 only, or an external firewall as the actual boundary are not affected.
Credit
This issue was originally reported in GHSA-r37j-mv37-rjpc. That advisory could not be used because the reporting account was deleted.
Пакеты
php
>=8.2.0, <8.2.34
8.2.34
php
>=8.3.0, <8.3.35
8.3.35
php
>=8.4.0, <8.4.26
8.4.26
php
>=8.5.0, <8.5.11
8.5.11
Связанные уязвимости
The IPv6 branch of the FastCGI client access check compares only the first 12 bytes of a 16-byte IPv6 address, so listen.allowed_clients matches on a /96 prefix instead of the exact address. An attacker who can source an address sharing the first 96 bits with an allowed one passes the check and reaches the FastCGI endpoint.
The IPv6 branch of the FastCGI client access check compares only the first 12 bytes of a 16-byte IPv6 address, so listen.allowed_clients matches on a /96 prefix instead of the exact address. An attacker who can source an address sharing the first 96 bits with an allowed one passes the check and reaches the FastCGI endpoint.
The IPv6 branch of the FastCGI client access check compares only the first 12 bytes of a 16-byte IPv6 address, so listen.allowed_clients matches on a /96 prefix instead of the exact address. An attacker who can source an address sharing the first 96 bits with an allowed one passes the check and reaches the FastCGI endpoint.
IPv6 ACL bypass in FastCGI listen.allowed_clients due to partial address comparison (memcmp 12 bytes)
The IPv6 branch of the FastCGI client access check compares only the f ...