# php2shell-mitigation.conf - Apache 2.4 stopgap for PHP2Shell # (CVE-2026-109618 / CVE-2026-109619) on servers that cannot get a fixed PHP yet. # # Published by NOC.org: https://noc.org/articles/php2shell-what-to-do-when-you-cant-just-upgrade # # This is NOT a fix. A patched PHP is the fix. This blocks the request shape the # exploit needs before mod_php sees it, and applies the PHP settings the advisory # recommends. # # PHP-FPM users: the blocks at the bottom do nothing for FPM. The request # blocks (1-3) still apply. Put the PHP values in the pool config as # php_admin_value[...] / php_admin_flag[...] and restart the FPM service. # # Install (Debian/Ubuntu): # cp php2shell-mitigation.conf.txt /etc/apache2/conf-available/php2shell-mitigation.conf # a2enconf php2shell-mitigation && { apache2ctl configtest && systemctl restart apache2 || { a2disconf php2shell-mitigation; systemctl restart apache2; }; } # Install (RHEL family): copy to /etc/httpd/conf.d/php2shell-mitigation.conf, then # apachectl configtest && systemctl restart httpd # Roll back: # a2disconf php2shell-mitigation && systemctl restart apache2 # # BEFORE enabling: find out whether anything legitimate POSTs multipart/form-data to # this server (file uploads, and JavaScript that posts a FormData object). Those # requests will get a 403. Scope the first block to specific paths if needed. # Block 2 also refuses any POST without a Content-Length, which some API clients, # mobile apps and HTTP/2 clients send. Block 3 caps EVERY request body at 8 MB, # including JSON API calls. Test the paths your users depend on after enabling. # 1. Refuse multipart bodies. This is the delivery vehicle for the exploit. # Unanchored on purpose, to catch Content-Type variants PHP may still parse. # A vhost-level / with "Require all granted" can override this: # probe every vhost after enabling. Require all denied # 2. Refuse POST/PUT/PATCH with no Content-Length (chunked bodies slip past post_max_size). Require all denied # 3. Cap request size and header count. Note: LimitRequestFields counts HTTP request # headers only. The header-flood variant of the exploit hides its headers inside # the multipart body, so rule 1 is what stops it, not this cap. LimitRequestBody 8388608 LimitRequestFields 60 LimitRequestFieldSize 8190 LimitRequestLine 8190 # 4. PHP settings from the advisory. The module name differs by PHP major version: # PHP 5 = mod_php5.c, PHP 7 = mod_php7.c, PHP 8 = mod_php.c. Keep all three so a # later PHP upgrade does not silently drop these settings. php_admin_flag session.upload_progress.enabled Off php_admin_value memory_limit -1 php_admin_value post_max_size 8M php_admin_value max_input_nesting_level 16 php_admin_value max_input_vars 1000 php_admin_value max_file_uploads 1 php_admin_flag session.upload_progress.enabled Off php_admin_value memory_limit -1 php_admin_value post_max_size 8M php_admin_value max_input_nesting_level 16 php_admin_value max_input_vars 1000 php_admin_value max_file_uploads 1 php_admin_flag session.upload_progress.enabled Off php_admin_value memory_limit -1 php_admin_value post_max_size 8M php_admin_value max_input_nesting_level 16 php_admin_value max_input_vars 1000 php_admin_value max_file_uploads 1 # memory_limit -1 is what the advisory asks for: the exploit needs a finite limit to # hit the out-of-memory path cleanly. The trade-off is that one runaway request can # now use all the RAM on the box. If that worries you, cap Apache with a systemd/cgroup # MemoryMax. Do NOT use RLIMIT_AS / RLIMIT_DATA: a failing mmap puts you back on the # out-of-memory path this is trying to avoid.