Two bugs, one root problem: request state
CVE-2026-109618: an out-of-memory at the wrong moment
- a multipart body with no usable declared length, which passes the initial size check;
- hundreds of headers per part, since their in-memory PHP representation weighs far more than the bytes on the wire;
- empty file inputs, which are not counted in max_file_uploads but still allocate their metadata arrays;
- deeply nested field names such as a[b][c][d][e].
CVE-2026-109619: the worker that remembers
- INI diagnostics raised before request initialization: an invalid php_value in a .htaccess, or a PHP_VALUE sent by nginx over FastCGI. @ and error_reporting change nothing here;
- session.upload_progress, enabled by default, which can consume session state left over by the previous request without any session_start() in your code.
Who is actually exposed
- Apache mod_php: full exploit demonstrated on 109618, RCE confirmed on 109619.
- PHP-FPM: vulnerable to 109618 per code analysis, upload_progress path validated on 109619.
- FrankenPHP: likely vulnerable to 109618, no known early consumer for 109619 with the default config.
- LSAPI: likely vulnerable to 109618, conditional candidate for 109619.
Patch, then check what really answers
Bash
# Debian / Ubuntu
sudo apt update && sudo apt install --only-upgrade 'php8.*'
# Without a restart, running workers keep the old code in memory
sudo systemctl restart php8.3-fpm
- php -v asks the CLI, which is not necessarily the binary serving the site. Ask the FPM binary directly;
- distributions backport fixes without bumping the version number. Ubuntu 24.04 still prints 8.3.6 after the fix. The package version is what matters.
Bash
php-fpm8.3 -v
dpkg -l | grep -E 'php8\.[0-9]-(fpm|cli|common)'
apt changelog php8.3-fpm | grep -m3 'CVE-2026-1096'
Until the patch lands: reduce, don't fix
Ini
; /etc/php/8.3/fpm/pool.d/www.conf
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M
php_admin_value[max_input_nesting_level] = 16
php_admin_flag[session.upload_progress.enabled] = off
; PHP 8.5 only
php_admin_flag[fatal_error_backtraces] = off
Nginx
client_max_body_size 8m;
location / {
if ($content_type ~* "multipart/form-data") {
return 415;
}
try_files $uri /index.php$is_args$args;
}
location /api/uploads {
try_files $uri /index.php$is_args$args;
}
The traps
- memory_limit=-1 removes the guard rail. A single request can eat all the container's RAM. Put a cgroup limit in front (mem_limit in Compose, resources.limits in Kubernetes) and remove the setting once patched. Do not use ulimit -v / RLIMIT_AS: the advisory explains that it makes mmap() fail and reopens the vulnerable path.
- Application-level maintenance mode protects nothing. A maintenance bundle or WordPress's .maintenance file runs after PHP has already parsed the request. Real maintenance means a static page served by nginx without going through FPM.
- Cloudflare's temporary WAF rules only help if your origin refuses direct traffic. Otherwise the attacker just hits the server IP.