On 21 October 2026 at 14:00 UTC, the PHP team shipped emergency releases for four branches at once, fixing two critical flaws rated 9.2 on CVSS 4.0 that researchers named PHP2Shell. Both allow unauthenticated , and both live in the PHP core itself, not in a framework or plugin, while PHP runs close to 70% of the websites whose server-side language is known, according to W3Techs. The Wikimedia Foundation switched off several services as a precaution, government bodies in Europe and Canada took official portals offline until they were patched, and Amazon’s honeypots logged the first exploitation attempt four days before the announcement. This article covers what happened day by day, how the two flaws work, how to check and patch your server or shield it for now, how to look for signs of compromise, and how we handled it at Illevante.
01What happened, day by day
The story starts in August 2026, when a researcher at Calif found one of the flaws while auditing PHP’s source with the help of an AI model, and a team at Wiz reached both independently with similar tooling; the PHP security team was notified under coordinated disclosure. Wiz coined the name PHP2Shell, because the exploit ends in a command shell on the server. Then events moved faster than the plan:
| Date (2026) | What happened |
|---|---|
| 17 October | Amazon’s MadPot honeypot network logs the first exploitation attempt, from an address on a commercial anonymising network, while both flaws are still under embargo. |
| 20 October | The PHP team pre-announces a critical security release for the next day, and details leak on Hacker News that same evening. |
| 21 October | Both advisories go up on GitHub at 13:34 UTC, then releases 8.5.12, 8.4.27, 8.3.36 and 8.2.35, plus source patches for 7.4, 8.0 and 8.1. watchTowr sees real exploitation in its honeypots the same day. |
| 22 October | Several public exploits appear, each working only on a specific mix of operating system, PHP version and web server, according to Rapid7. |
| 23 October | The PHP team publishes a third advisory, CVE-2026-115560, with no patch yet, and recommends disabling upload progress tracking. The FBI urges organisations to update or take servers offline. |
| 26 October | CISA adds both flaws to its Known Exploited Vulnerabilities (KEV) catalogue and gives US federal agencies until 29 October. |
The first lesson of this table is that the gap between announcement and exploitation was not days but hours, or less. Any server exposed since 17 October should be treated as having been probed until proven otherwise.

02How the two flaws work
Both flaws share one pattern: a fatal error interrupts an operation halfway through, and PHP later treats the half-finished state as valid. Worse, this happens while the request is being parsed, before any script runs, so neither a login page nor any check in your application’s code can protect you.
CVE-2026-109618: half-initialised memory
When a POST form arrives, PHP turns its fields into the $_POST array and stores them in a hash table. In the affected code, PHP bumps the element counter before allocating memory for the new key, so if that allocation exceeds memory_limit the insert aborts and leaves a counted slot that was never initialised. At request shutdown, PHP walks every counted slot to free it and reads whatever stale bytes are left in that one. An attacker crafts a multipart request that exhausts memory at the right moment, arranges what is left behind, and steers the cleanup routine into running a command of their choice.

CVE-2026-109619: state stuck between two requests
A single PHP worker serves requests one after another, and some of its global fields point to memory owned by the current request. If a fatal error interrupts their reset, or a late diagnostic recreates them after cleanup, they keep pointing at freed memory, and the next request on the same worker reads it. This is a , and the PHP team confirmed two paths to it:
- File upload progress tracking: session.upload_progress is on by default and runs even if your application never calls session_start. Exploitation through it was confirmed on mod_php and on PHP-FPM.
- Fatal error backtraces in PHP 8.5: The fatal_error_backtraces setting is on by default in this branch, and exploitation through it was confirmed on mod_php.
Detection is harder because a successful exploit does not necessarily crash the worker: in Datadog’s lab the response came back as HTTP 200 and the worker carried on. No crashes in your logs is not proof that you are safe.
03Who is affected
Every PHP installation that serves web requests is affected, whatever connects it to the web server: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. The PHP team rates mod_php as the highest risk, because there the exploit completes with an anonymous request before any application code runs. Both flaws reach every version from 5.4 to 8.5:
| Branch | Affected versions | Fix |
|---|---|---|
| 8.5 | Before 8.5.12 | 8.5.12 |
| 8.4 | Before 8.4.27 | 8.4.27 |
| 8.3 | Before 8.3.36 | 8.3.36 |
| 8.2 | Before 8.2.35 | 8.2.35; the branch reaches end of life on 31 December 2026 |
| 7.4, 8.0 and 8.1 | All | No official release: limited source patches, or patched packages from your distribution or an extended-support vendor |
| 5.4 to 7.3 | All | Nothing from PHP itself: extended-support packages, or an upgrade to a supported branch |
Watch for three cases where a quick check will mislead you:
- The command line is not the website: php -v reads the CLI build, while the site may run a different version through FPM, LiteSpeed or PHP Selector.
- Patched without a new number: Distributions, CloudLinux and extended-support vendors sometimes patch while keeping the version number, so the package changelog is the yardstick, not the number.
- Bundled PHP: Containers and some appliances and applications ship their own copy of PHP, which operating system updates never reach.
04Check your server and patch it
Start with internet-facing servers and anything running mod_php. The first step is to learn what actually serves your site, then to look for the two CVE numbers in the package changelog:
# The version and SAPI as your site sees them, not the command line (delete the file right after)echo '<?php echo PHP_VERSION, " ", PHP_SAPI, " upload_progress=", ini_get("session.upload_progress.enabled");' | sudo tee /var/www/html/ver-91c.php >/dev/nullcurl -s https://dukkan-sohar.com/ver-91c.php; echosudo rm /var/www/html/ver-91c.php# Debian and Ubuntu: does the package carry the fix? Change 8.3 to your branchapt changelog php8.3-fpm 2>/dev/null | grep -E 'CVE-2026-10961[89]'# RHEL, AlmaLinux and CloudLinux: the OS package, then the alt-php package for each branch you userpm -q --changelog php-cli | grep -E 'CVE-2026-10961[89]'rpm -q --changelog alt-php74-common | grep -E 'CVE-2026-10961[89]'
If the fix does not show up, update. Updating alone is not enough: old workers stay in memory with the vulnerable code, and the new code is not loaded until you restart all of them.
# Debian and Ubuntu: update, then restart everything that runs PHP (adjust the names to your server)sudo apt update && sudo apt upgradesudo systemctl restart php8.3-fpm apache2# RHEL and AlmaLinuxsudo dnf upgrade 'php*'sudo systemctl restart php-fpm httpd# cPanel on CloudLinux: EasyApache and alt-php packages, then Apache and PHP-FPMsudo dnf clean all && sudo dnf upgrade 'ea-php*' 'alt-php*'sudo /scripts/restartsrv_httpd && sudo /scripts/restartsrv_apache_php_fpm# LiteSpeed: restart the server, then the detached lsphp processes that survive itsudo /usr/local/lsws/bin/lswsctrl restartsudo touch /usr/local/lsws/admin/tmp/.lsphp_restart.txt# Docker: rebuild pulling the patched base image; a restart is not enoughdocker compose build --pull && docker compose up -d
- ProblemI updated PHP and the web check still shows the old version
- FixThe old workers are still in memory. Restart PHP-FPM or Apache, and on LiteSpeed restart the detached lsphp processes too.
- Problemphp -v shows a patched version but the site runs another
- FixThe command line is not what serves the site. Check through the web, and update the version selected in the control panel or the server configuration.
- ProblemAfter updating, the version number is lower than the table’s
- FixDistribution and CloudLinux packages sometimes patch without changing the number. Look for the two CVE numbers in the package changelog.
- ProblemI restarted the container and it is still exposed
- FixThe container runs from the same image. Rebuild pulling the patched base image, then deploy the new image.
- ProblemAn upload form posted from another domain is now rejected after the new WAF rules
- FixSome rules reject uploads carrying the session progress field from outside the site. Add a narrow exception for that path after you patch PHP, not before.
05If you cannot update today
The PHP team published temporary measures and stressed that they are no substitute for updating. Use them to buy hours, not weeks:
- Disable upload progress tracking: Set session.upload_progress.enabled to 0 in php.ini or in the FPM pool configuration, then restart every worker. This closes the known path for CVE-2026-109619 and the path for CVE-2026-115560 too; ini_set and .user.ini files are not enough.
- On PHP 8.5: Set fatal_error_backtraces to 0; it closes the fatal-error backtrace path only.
- Shrink the request size: post_max_size of 8M if you need no uploads or 32M if you do, with upload_max_filesize below it, plus limits in the web server or proxy on the number of fields, parts and headers that apply even when Content-Length is missing.
- memory_limit of -1, carefully: It prevents the exhaustion CVE-2026-109618 needs, but leaves memory uncapped, so pair it with a container or cgroup memory limit that kills only the affected worker. Do not use RLIMIT_AS or RLIMIT_DATA, which trigger the very allocation failure the bug relies on.
- WAF rules: Cloudflare, AWS WAF and Imunify360 published rules that block the known exploit tools. This is a virtual patch that stops whatever resembles the published tools, and it does not cover the memory-exhaustion path.
- Take it off the internet: If none of the above is possible, the FBI and CERT-EU advised isolating the application from public networks or shutting it down until it is patched.
# /etc/php/8.3/fpm/pool.d/www.conf: temporary measures until you update, not a substitute for itphp_admin_value[session.upload_progress.enabled] = 0# PHP 8.5 onlyphp_admin_value[fatal_error_backtraces] = 0# 8M with no uploads, or 32M if you need them; a single file stays below the request sizephp_admin_value[post_max_size] = 8Mphp_admin_value[upload_max_filesize] = 6M# nginx, inside the server block: the same cap before the request reaches PHPclient_max_body_size 8m;# Then restart every worker, and read the effective value through the web as in the check abovesudo systemctl restart php8.3-fpm nginx
06Has your server been compromised?
Updating shuts the door; it does not evict whoever came in before. Exploitation goes through stages that leave traces in the logs even when it succeeds without a crash:
- Exhaustion before any script: An Allowed memory size message ending in in Unknown on line 0 means memory ran out while the request was being set up. A short burst of them on one worker is a sign of attack preparation, although exhaustion alone does not prove an attack.
- Worker crashes: Segmentation fault or SIGSEGV messages in the Apache or FPM log around the same time.
- Odd upload requests: PHPSESSID and PHP_SESSION_UPLOAD_PROGRESS fields on pages that take no uploads, thousands of parts in a single request, or file names containing <?php or |O:.
- A web worker spawning a shell: The clearest evidence of all: apache2, php-fpm or lsphp as the parent of an sh, bash or curl process.
# Memory exhaustion during request setup: how many messages end in Unknown on line 0sudo grep -h "Allowed memory size" /var/log/php*-fpm.log /var/log/nginx/error.log /var/log/apache2/error.log 2>/dev/null | grep -c "Unknown on line 0"# Worker crashes, the last 20sudo grep -hE "Segmentation fault|SIGSEGV" /var/log/php*-fpm.log /var/log/apache2/error.log 2>/dev/null | tail -n 20# PHP files created or changed since 17 October: review the list line by linesudo find /var/www -type f -name '*.php' -newermt 2026-10-17 -printf '%TY-%Tm-%Td %TH:%TM %p\n' | sort# Shell or download processes a web worker is running right nowps -ef --forest | grep -A3 -E 'php-fpm|apache2|httpd|lsphp' | grep -E ' (sh|bash|curl|wget|nc) '

07How Illevante handled PHP2Shell
Illevante’s cPanel hosting servers run CloudLinux, the LiteSpeed web server and Imunify360, and every customer picks their site’s PHP version, from 5.6 to 8.5, through PHP Selector. That means our servers carry branches for which PHP itself released no fix at all, so waiting for the official release was not an option. Here is how we followed it through:
- 01The alert: Our server monitoring team picked up the pre-announcement on 20 October and began inventorying versions and SAPIs on every server before the details came out.
- 02Protection before the patch: Imunify360 rules reached the servers automatically and were blocking the published exploit tool about an hour before the official release.
- 03Patching: We installed the patched alt-php and ea-php packages as soon as they reached the stable repositories, then restarted every lsphp process so the new code would load.
- 04Verification: We did not stop at the version number: we looked for the two CVE numbers in each package’s changelog and checked the version actually serving the sites.
- 05Investigation: We reviewed error and process logs back to 17 October for the signs above, and Imunify360 scanned site files for backdoors.
No hosting customer had to change their PHP version or touch their site’s code. Here is what made that possible:
Imunify360: protection before the patch
Imunify360 rules reach the servers without any action on our side. Ruleset 8.83 shipped at 09:08 UTC on 21 October and blocked the published exploit request on Apache and nginx; 8.84 followed at 13:07 UTC and blocked the exploit tool on every web server including LiteSpeed, 53 minutes before the official release. Later rulesets closed modified versions of the tool, up to 8.88 on 23 October, with a CAPTCHA challenge for addresses that scan servers or attempt exploitation. We treat these rules for what they are: a virtual patch that bought hours, not a substitute for the fix, since they do not cover the memory-exhaustion path.
CloudLinux: every version patched, even the old ones
This is the biggest difference. PHP itself patched four branches, left 7.4, 8.0 and 8.1 with source patches, and gave nothing to anything older. CloudLinux, by contrast, pushed patched ea-php packages from 5.2 to 8.5 to its stable repository by 20:55 UTC on 21 October, and alt-php packages for every branch from 5.2 to 8.5 at 00:07 UTC on 22 October. A site running PHP 7.4 stayed on 7.4, patched, with no forced upgrade that might break its code.
CloudLinux isolation also limits the damage if an attack lands before the patch. The exploit runs its commands with the worker’s privileges, that is, as the site account’s user, and locks that user inside a file system of its own, so it cannot see other accounts’ files on the server. LVE caps each account’s CPU and memory, so a flood of exhaustion requests against one site does not slow its neighbours.
Round-the-clock follow-up
Events did not wait for office hours: the official release landed at 6 pm Dubai time, the alt-php packages at 4 am, and the CVE-2026-115560 advisory two days later. Our server and network monitoring runs 24/7, so each update was applied when it arrived, not the next morning. If you have a CoreVPS with root access, its PHP version is in your hands: the commands above are all you need, the Imunify360 + CloudLinux security bundle brings your server the same rules and the same patched packages, and our technical support is with you around the clock if you need a hand.

08After PHP2Shell
Patching closed the two known paths, but Datadog points out that the pattern behind them, updating metadata before allocating the memory it describes, is still in the code, and expects new instances to surface. CVE-2026-115560 is still waiting for its fix in the next release. Three steps make the next time easier:
- 01A standing inventory: Know, for every server and container, which PHP version serves requests, where its package comes from and who updates it. An inventory built during the crisis arrives too late.
- 02A supported branch: Plan now to leave 8.1 and older, and 8.2 before its end of life on 31 December 2026, or rely on a vendor that patches your old branches the way CloudLinux did.
- 03Upload progress tracking off: Keep it that way unless your application genuinely depends on it; it is the path shared by two of the three flaws.
And restart the workers after every PHP update as part of the update itself, not as a step you remember later.




