Introduction
On 21 October 2026, the PHP security team released fixed versions for two critical vulnerabilities located in the core of the language (not in an application or a third-party library): CVE-2026-109618 and CVE-2026-109619. Both are rated 9.2 on CVSS v4.0 (a score assigned by the PHP team at the time of writing, which may change with later analyses, notably the NVD's) and can lead to unauthenticated remote code execution (RCE) with the privileges of the web process.
The event has been nicknamed "PHP2Shell" by the community. The disclosure timeline is unusual: the CVE identifiers leaked before publication, the PHP team brought the release forward by one day, and Amazon Threat Intelligence reports an exploitation attempt as early as 17 October, four days before disclosure.
In this article we summarise what is known so far, explain the mechanism without publishing any exploit code, and above all provide commands so that you can check your own exposure.
Please note: the situation is evolving quickly. This article is based on PHP's official advisories and on publications by security vendors as of 22 October 2026. Sources are listed at the end of the article.
TLDR: the essentials in 30 seconds
- What: two critical flaws in the PHP core, exploitable without authentication to execute code remotely.
- Who: any PHP installation serving web requests and older than 8.5.12, 8.4.27, 8.3.36 or 8.2.35. The 8.1 and earlier branches have no official fix.
- What to do: update and restart the workers, the only complete fix.
- Check for yourself: 1. inventory PHP · 2. compare versions · 3. check backports · 4. check configuration · 5. identify external exposure · 6. look for signs of exploitation
At a glance
| Key point | Detail |
|---|---|
| CVE | CVE-2026-109618 (memory corruption on out-of-memory) and CVE-2026-109619 (stale global state, use-after-free) |
| Severity | Critical, CVSS v4.0 9.2 for both (some identifiers circulated before publication stated 10.0, which the PHP team says is incorrect) |
| Impact | Unauthenticated code execution with the privileges of the web worker, memory disclosure (passwords, tokens, API keys, certificates) |
| Fixed versions | PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 |
| Affected versions | Any version older than the fixes, including the 8.1 and earlier branches, which will not receive any. Sources mention vulnerable code going back to PHP 5.4 |
| Most exposed configuration | Apache with mod_php |
| Complete workaround? | No. Only updating truly fixes the issue |
What happens technically?
Both flaws belong to the same family: the handling of request state that survives, or is improperly initialised, after a fatal error. We rely here on PHP's official advisories (GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp).
CVE-2026-109618: memory corruption on out-of-memory
When PHP runs out of memory (Out Of Memory, OOM), it aborts the request with a longjmp(). In some contexts, request memory is already reachable while it is not fully initialised. The abort then leaves invalid structures behind, which the end-of-request cleanup phase reads as if they were valid.
Several behaviours of multipart request parsing make it possible to trigger this out-of-memory condition while staying within PHP's apparent limits: missing content-length handling, an unlimited number of headers per part, empty file controls with nested field names. An application-specific OOM condition can also be enough.
CVE-2026-109619: stale global state (use-after-free)
PHP keeps some request-related global state (errors, sessions, objects, extensions) after the underlying memory has been freed. In a persistent worker, code that runs before normal request initialisation can reuse this stale pointer. Exploitation requires two requests handled by the same worker: one that leaves the stale state behind, and one that consumes it.
PHP confirms two sources of stale state:
- file upload progress in sessions (
session.upload_progress), enabled by default, validated on mod_php and PHP-FPM; - fatal error backtraces (
fatal_error_backtraces) in PHP 8.5, which underpin the confirmed exploit on mod_php.
JFrog describes the exploitation mechanism of upload progress: the attacker influences the session ID, a key suffix and the name of the uploaded file, which are serialised into a session file. PHP deserialises that file before any script runs. An error that bypasses the cleanup leaves pointers to freed memory; a second request on the same worker reuses that memory with attacker-controlled bytes.
A note on attribution: Wiz and JFrog attribute the upload-progress use-after-free to CVE-2026-109618, and the error backtraces and OPcache to CVE-2026-109619. PHP's GHSA advisories, which we follow here, describe CVE-2026-109618 as an OOM-handling issue and CVE-2026-109619 as a stale-state issue, with upload progress appearing in the latter. Wiz also states that other variants should fall under the same two CVEs. In practice the distinction has no impact on remediation: both are fixed by the same versions.
The discovery is credited to Wiz Research, in coordination with Calif and OpenAI, with a significant share of AI-assisted analysis. Wiz says it identified the first bug on 19 August 2026 and reported everything to the PHP team on 24 August, almost two months before publication.
Why it is serious
- The application code does not need to be vulnerable. The engine itself is at fault: a fully up-to-date WordPress, Drupal or in-house application running on a vulnerable PHP is exposed.
- Exploitation happens through an HTTP request, before authentication. PHP validated a full RCE on mod_php with default memory and input limits.
- All web SAPIs are affected: PHP-FPM, mod_php, php-cgi, FrankenPHP and LiteSpeed. mod_php is the most exposed (full exploit validated by PHP). For PHP-FPM, the upload-progress path has been validated, and the other flaw appears exploitable without a complete exploit having been demonstrated. FrankenPHP and LSAPI are likely affected based on source code analysis.
- The scope goes beyond your web servers: PHP is embedded in vendor appliances and software (hosting control panels, appliances, administration tools). Cisco, for instance, has published a dedicated advisory.
Exploitation status
Sources are not fully aligned, which is normal at this stage:
- Amazon Threat Intelligence observed an attempt on its honeypots on 17 October, followed by others on 21 October with payloads and infrastructure suggesting several actors. Amazon recommends investigating from 17 October onwards.
- Tenable reports broad exploitation in the wild. Rapid7 is more cautious and expects a working exploit to be published soon. Huntress had not observed exploitation among its partners as of 21 October.
- Public exploitation tools now exist. Sansec states that technical write-ups and an exploit tool, credited to Calif, are public. Calif demonstrated unauthenticated code execution on Magento 2.4.9 and ten other PHP applications, and watchTowr confirmed RCE as early as 20 October. Cloudflare observed early attacks on 21 October at 07:43 UTC, before the fixes were released. Several GitHub repositories also present themselves as PoCs, but their reliability is not established: Huntress was not aware of any public code when it published, and JFrog reports fake PoCs, one of which embeds a Base64 payload. Never run an unaudited PoC, and certainly not in a production environment. We deliberately do not reference any of these repositories.
Our recommendation is to treat the vulnerability as exploited and to prioritise Internet-facing servers.
Am I vulnerable? Verification commands
The commands below are purely defensive: they read versions, configurations and processes. They do not exploit the flaw. Run them on your own systems, with an account that has the appropriate rights.
Step 1: inventory all PHP installations
A machine may hold several PHPs (CLI, FPM, Apache module, containers, software bundling its own PHP). Start by listing them.
# PHP, PHP-FPM, php-cgi binaries and Apache modules present on the system
find / \( -name 'php' -o -name 'php[0-9]*' -o -name 'php-fpm*' -o -name 'php-cgi*' -o -name 'libphp*.so' \) -type f -perm -u+x -not -path '/proc/*' 2>/dev/null
# Version of each SAPI
php -v
php-fpm -v 2>/dev/null # or php-fpm8.3 -v depending on the distribution
php-cgi -v 2>/dev/null
# PHP module loaded by Apache
apache2ctl -M 2>/dev/null | grep -i php # httpd -M on RHEL and derivatives
On Windows (PowerShell):
Get-ChildItem -Path C:\ -Recurse -Include php.exe,php-cgi.exe -ErrorAction SilentlyContinue |
ForEach-Object { "$($_.FullName) : $((& $_.FullName -v)[0])" }
For containers:
# PHP version in each running container (container ID: version)
# The matching name can be obtained with "docker ps".
for c in $(docker ps -q); do
echo "$c : $(docker exec $c php -v 2>/dev/null | head -1)"
done
Do not stop at running containers: also check the base images in your registries and CI/CD pipelines. If you use a "patched" image provided by a third party, check the PHP version inside it: some community images carry tags lower than the fixed versions (for example 8.3.35 instead of 8.3.36), which is not enough to conclude that they are protected.
About online testers: public PHP2Shell checkers exist. Only use them on sites you own, and be aware that a negative result does not prove that you are up to date. Aikido states that its definitive test sends a payload that may crash a PHP worker. Prefer the version and package changelog checks described here, and avoid running any such test on a production system during business hours.
Step 2: compare with the fixed versions
You are likely vulnerable if your version is lower than:
| Branch | First fixed version |
|---|---|
| PHP 8.5 | 8.5.12 |
| PHP 8.4 | 8.4.27 |
| PHP 8.3 | 8.3.36 |
| PHP 8.2 | 8.2.35 |
| PHP 8.1 and earlier | No official release. Minimal source backports for 7.4, 8.0 and 8.1, or a fix from your distribution / LTS vendor |
Step 3: beware of distribution backports
This is the classic trap. Debian, Ubuntu, RHEL and derivatives often fix issues without changing the upstream version number. A package showing 8.2.29 may therefore already contain the fix: a number lower than those in the table is not enough to conclude that a distribution package is vulnerable, just as an identical number does not prove that it is protected. Check the package changelog:
# Debian / Ubuntu
apt changelog php8.3-fpm 2>/dev/null | grep -E 'CVE-2026-10961[89]'
dpkg -l | grep -E '^ii\s+php'
# RHEL, Fedora, Rocky, Alma
rpm -q --changelog php-fpm | grep -E 'CVE-2026-10961[89]'
rpm -qa | grep -i '^php'
# Alpine (the package carries no CVE changelog: compare with the Alpine security tracker)
apk list -I 'php*'
If both CVEs appear in the changelog of the installed package, the fix is present in that package (provided the processes have been restarted, see below). If in doubt, consult your distribution's security advisory and update: apt update && apt install --only-upgrade 'php*', or dnf upgrade 'php*'.
For third-party repositories (for example Remi), the Huntress team noted that packages were not yet up to date on the day of publication. Check availability before drawing conclusions.
Step 4: check mitigation settings (if you cannot update immediately)
These settings reduce the known attack surface, but are not a fix (see below).
# Values present in PHP configuration files (ignore lines commented out with ";")
grep -rnE 'session\.upload_progress\.enabled|fatal_error_backtraces|^\s*memory_limit' /etc/php* /usr/local/etc/php* 2>/dev/null
# Value from the PHP-FPM php.ini (the CLI one is often different! on Debian: php-fpm8.3 -i)
php-fpm -i 2>/dev/null | grep -E 'session.upload_progress.enabled|fatal_error_backtraces|^memory_limit'
This command does not see per-pool PHP-FPM overrides (php_admin_value[...] in /etc/php/*/fpm/pool.d/, already covered by the first grep). For mod_php, the effective value is that of the php.ini in the apache2 folder (e.g. /etc/php/8.3/apache2/php.ini) plus the directives set in the Apache configuration and in .htaccess files, where a php_value can restore a value you had hardened (only php_admin_value cannot be overridden):
grep -rnEi 'php_(admin_)?(value|flag)' /etc/apache2 /etc/httpd /var/www --include='*.conf' --include='.htaccess' 2>/dev/null | grep -iE 'upload_progress|memory_limit|fatal_error'
Step 5: identify external exposure
To identify what potentially exposes PHP on the Internet side (indicative detection only, the header is often hidden):
curl -sI https://your-site.example | grep -iE '^(server|x-powered-by):'
A missing header proves nothing. Do not rely on this method to conclude that you are not exposed. Inventory and attack surface management tools (authenticated scanners, EASM) are more reliable. Tenable, for example, provides the Nessus plugin 369483 ("PHP < 8.2.35 / 8.3.x < 8.3.36 / 8.4.x < 8.4.27 / 8.5.x < 8.5.12 Multiple Vulnerabilities"), which relies only on the version reported by PHP and therefore does not test the flaw (a distribution package fixed through a backport may show up as a false positive), and runZero offers the query vendor:=PHP AND product:=PHP in its software inventory.
How do I know if I have already been targeted?
Vendors have not yet published confirmed indicators of compromise (IOCs). The most reliable detection to date relies on behaviour: a PHP or web server process that launches a command interpreter or a download tool.
# Processes launched BY a PHP / Apache worker (excluding the workers themselves): to be reviewed
# (sh, bash, python, perl, curl, wget, nc...). A few legitimate results are normal
# (convert, git, sendmail...): what matters is what is new and inconsistent.
ps -eo pid=,ppid=,user=,comm=,args= | awk '
{ comm[$1]=$4; line[$1]=$0; ppid[$1]=$2 }
END { for (p in ppid)
if (comm[ppid[p]] ~ /^(php|apache2|httpd)/ && comm[p] !~ /^(php|apache2|httpd)/) print line[p] }'
# Reverse shell patterns in logs and web roots
# (fsockopen exists in legitimate code, notably in vendor/: needs triage)
grep -rEl '/dev/tcp/|fsockopen|mkfifo' /var/log/apache2 /var/log/nginx /var/log/httpd /var/www 2>/dev/null
# PHP files modified in the last 5 days (compare with your deployment dates)
find /var/www /srv /home -name '*.php' -mtime -5 2>/dev/null
On Windows:
$php = Get-CimInstance Win32_Process | Where-Object { $_.Name -in 'php-cgi.exe','php.exe' } | Select-Object -ExpandProperty ProcessId
Get-CimInstance Win32_Process | Where-Object { $_.ParentProcessId -in $php -and $_.Name -in 'cmd.exe','powershell.exe' }
Given the first attempt observed on 17 October, extend your log analysis back to that date at the very least. Look in particular for:
- unusual
multipart/form-datarequests (very many headers, large bodies, nested fields) followed by worker restarts or errors; - abnormally large or malformed
PHPSESSIDcookies in responses, as well as series of HTTP 500 responses after multipart requests (indicators reported by Wiz and Aikido); - memory errors or abnormal terminations of PHP processes (
segfaultindmesgor system logs, Allowed memory size exhausted messages, workers killed by the kernel); - classic reconnaissance commands run from the web account:
id,whoami,uname -a.
⚠️ The absence of these elements does not prove the absence of a compromise. The vector may leave few application traces, and PHP itself states that clean error logs are not sufficient proof.
If you suspect a compromise: isolate the server, keep the logs, look for webshells, new scheduled tasks and new accounts, then rotate the secrets (passwords, tokens, API keys, certificates) accessible to the application, since the flaw may disclose memory contents.
The CERT-FR position
CERT-FR published the alert CERTFR-2026-ALE-014 on 21 October 2026. It classifies the risk as arbitrary remote code execution and lists the following affected systems:
- all 5.x, 6.x, 7.x, 8.0.x and 8.1.x versions, which are no longer supported by the vendor;
- PHP 8.2.x before 8.2.35, 8.3.x before 8.3.36, 8.4.x before 8.4.27 and 8.5.x before 8.5.12.
CERT-FR recommends isolating the affected systems, then migrating to a maintained version. It also suggests two interim measures, which it states it has not verified:
- disable file upload progress:
session.upload_progress.enabled = Offinphp.ini, orphp_admin_flag[session.upload_progress.enabled] = offfor PHP-FPM. This only reduces exposure to one of the two vulnerabilities; - block HTTP requests whose
Content-Typeheader ismultipart(through a WAF for instance) or, failing that, monitor them closely.
This second point is easy to check in the logs. If your web server records the Content-Type (a field to be added to the log format), you can isolate these requests:
# Apache example: add %{Content-Type}i to the LogFormat, then count multipart requests per source IP
grep -i 'multipart/form-data' /var/log/apache2/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
What to do?
1. Update (the only complete fix)
Move to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or to the fixed package from your distribution. PHP is clear on this point: no configuration constitutes a complete mitigation.
Restart the workers (systemctl restart php8.3-fpm, apache2, etc.): a fix that is installed but not reloaded does not protect processes already in memory. Then check that the processes started after the update, not just that the package is up to date on disk:
ps -eo pid,lstart,args | grep -E '[p]hp-fpm|[a]pache2|[h]ttpd' | head
2. If an immediate update is not possible
Apply, in addition and as a temporary measure:
session.upload_progress.enabled = 0;- on PHP 8.5:
fatal_error_backtraces = 0; memory_limit = -1enforced so that the application cannot override it, and without relying onRLIMIT_ASorRLIMIT_DATA(which causemmap()allocation failures on the vulnerable path). Be careful, this removes a protection against resource exhaustion: combine it with a memory limit at the container or cgroup level;- reduction of the request surface: a conservative
post_max_size, limits on the number of variables, multipart parts and nesting depth, or blocking multipart at the front end if the application does not need it; - if performance allows,
pm.max_requests = 1in PHP-FPM, which reduces the "same worker" condition; - restart of all workers after each change.
These measures are partial: disabling upload progress does not cover the backtrace path or the OPcache state, and removing the memory limit does not prevent all allocation failures. A WAF can help (Cloudflare has published rules covering known vectors), but Cloudflare itself describes it as a stopgap.
3. PHP 8.1 and earlier: getting out of the dead end
No official release is planned. The PHP team has provided minimal backports for the PHP 7.4, 8.0 and 8.1 sources, usable only if you compile PHP yourself. For branches 5.4 to 7.3, nothing exists on PHP's side. In all cases:
- check whether your distribution or an LTS vendor publishes a fix;
- remove these systems from the Internet or place them behind a VPN, a WAF or a reverse proxy that filters multipart;
- plan the migration to a supported branch. Note that PHP 8.2 will reach end of life on 31 December 2026: do not make it your long-term target.
4. Do not forget the "hidden PHPs"
The inventory must include embedded PHP: CMS (WordPress, Drupal, Joomla, Magento), hosting panels (cPanel, Plesk), appliances and administration tools, as well as container images. For each of them, the fix depends on the vendor: ask for its position and timeline. The Dutch NCSC maintains a public inventory of products embedding PHP for this purpose, with a status per product (under investigation, vulnerable, fixed, workaround, not vulnerable). It is still incomplete, its entries are leads to be confirmed with the vendor, and it does not cover your in-house PHP applications.
Our analysis
This episode illustrates several points we regularly encounter during our engagements:
- Patch management does not stop at the application. An up-to-date application on top of a vulnerable runtime remains exposed. Inventorying the underlying components (runtimes, modules, base images) is a prerequisite.
- "Forgotten" environments are the ones that will pay. PHP 7.x or 5.x kept for historical reasons, exposed staging servers and frozen container images will not get an official fixed package (at best, backports to compile yourself for 7.4 to 8.1).
- The window between leak and fix is shrinking. An exploitation attempt observed before disclosure shows that you need to be able to deploy in hours, not weeks.
- Behavioural detection compensates for the lack of IOCs. A web server that spawns a shell should trigger an alert, whatever vulnerability is exploited. It is also a good exercise to check what your monitoring really sees.
At AlgoSecure, we can help you map your exposure (with AlgoLightHouse, our EASM solution), verify exploitability on your exposed perimeters as part of a penetration test and assess the impact of a compromise through an Assumed Breach approach.
Sources
- php.net: announcements of 20 and 21 October 2026
- CERT-FR: CERTFR-2026-ALE-014
- GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp: official security advisories
- Wiz Research: PHP2Shell
- Aikido: Critical vulnerabilities in PHP
- NCSC-NL: php2shell
- Sansec: PHP2Shell
- JFrog Security Research: PHP2Shell
- Tenable: php2shell FAQ
- Huntress: Rapid Response php2shell
- runZero: Critical RCE vulnerability in PHP
- Rapid7: CVE-2026-109618 and CVE-2026-109619
- Cisco Security Advisory: PHP October 2026
À propos : Le blog d'AlgoSecure est un espace sur lequel notre équipe toute entière peut s'exprimer. Notre personnel marketing et commercial vous donne des informations sur la vie et l'évolution de notre société spécialisée en sécurité sur Lyon. Nos consultants techniques, entre deux tests d'intrusion ou analyses de risque, vous donnent leur avis ainsi que des détails techniques sur l'exploitation d'une faille de sécurité informatique. Ils vous expliqueront également comment sécuriser votre système d'informations ou vos usages informatiques particuliers, avec autant de méthodologie et de pédagogie que possible. Vous souhaitez retrouver sur ce blog des informations spécifiques sur certains sujets techniques ? N'hésitez pas à nous en faire part via notre formulaire de contact, nous lirons vos idées avec attention. Laissez-vous guider par nos rédacteurs : Alessio, Alexandre, Amine, Anas, Arnaud, Benjamin, Damien, Enzo, Eugénie, Fabien, Françoise, Gilles, Henri, Hicham, Jean-Charles, Jean-Philippe, Jonathan, Joël, Joëlie, Julien, Jéromine, Lucas, Ludovic, Lyse, Matt, Nancy, Natacha, Nicolas, Pierre, PierreG, Quentin, QuentinR, Sébastien, Thomas, Tristan, Yann, Yannick, et bonne visite !