Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
Home/Blog/php2shell (CVE-2026-109618, CVE-2026-109619): How Two PHP Engine Bugs Put 69% of the Web at Risk
Threat ResearchOctober 21, 202620 min read

php2shell (CVE-2026-109618, CVE-2026-109619): How Two PHP Engine Bugs Put 69% of the Web at Risk

Two PHP engine bugs, CVSS 9.2 each, give unauthenticated RCE before your code runs. The mechanism, the SAPI split, and why 34.8% of PHP sites get no patch.

O
O3 Security Team
php2shell (CVE-2026-109618, CVE-2026-109619): How Two PHP Engine Bugs Put 69% of the Web at Risk
Key takeaways
  • php2shell is two PHP interpreter bugs, CVE-2026-109618 (OOM corruption) and CVE-2026-109619 (stale global state), both CVSS v4.0 9.2, both giving unauthenticated RCE. Fixes shipped October 21, 2026 in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35.
  • The flaw is in the PHP engine, not your application. PHP demonstrated RCE on Apache mod_php before any application code runs, and Wiz achieved unauthenticated RCE on the latest WordPress, Drupal and Joomla images.
  • 34.8% of PHP sites run PHP 7, 5 or 4 (W3Techs, October 21, 2026), branches that will never get an official fix. Shodan counted about 884,000 internet-facing hosts on PHP 8.1 or older.
  • EOL branches are not entirely stranded: PHP published minimal source backports for 7.4, 8.0 and 8.1 in the official php-src repo. They are source only, so you must build them, and several vendor summaries wrongly say no fix exists.
  • Do not use RLIMIT_AS or RLIMIT_DATA as a mitigation. PHP's advisory warns they make mmap() fail and trigger the vulnerable longjmp() path, so that hardening makes exploitation easier.
  • The same OOM-skips-cleanup primitive was reported in 2019 and partially patched as CVE-2019-11048. The PHP Foundation calls the delay since then "a structural failure"; what AI changed was the cost of weaponising it, not the discovery.
  • In-the-wild evidence was genuinely split on day one: Cloudflare and Patchstack reported early attacks and probing, while Imperva, watchTowr and Huntress reported none and CISA's KEV had not listed either CVE.
  • Patching to PHP 8.2.35 buys about ten weeks. PHP 8.2 security support ends December 31, 2026, so plan the 8.3+ upgrade in the same effort.

Your application code was never the problem. You can have zero dependencies, a clean audit, no user input and a single line that prints "Hello World", and still hand an unauthenticated attacker a shell. That is what makes php2shell different from almost every PHP vulnerability before it.

php2shell is the shared name for two critical vulnerabilities in the PHP interpreter itself, CVE-2026-109618 and CVE-2026-109619, both rated CVSS v4.0 9.2 and both capable of unauthenticated remote code execution. PHP shipped emergency fixes on October 21, 2026 at 14:00 UTC, one day after the details leaked early. The bugs are in the engine that parses the request, not in any framework, plugin or library you chose.

Key takeaway

Patch to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, then restart every PHP worker. A config change without a restart leaves the vulnerable workers running. There is no config-only fix, and one popular hardening measure, RLIMIT_AS, actively makes the bug easier to trigger.

What actually broke: two bugs, one root cause

Both CVEs come from the same uncomfortable place. PHP is designed to survive a request that runs out of memory or hits a fatal error. It aborts that request, keeps the worker process alive and serves the next one. The abort path is where the safety assumptions fall apart.

CVE-2026-109618: RCE via OOM corruption

PHP uses longjmp() to bail out when a request exceeds memory_limit. longjmp() is a blunt instrument: it jumps out of the current context without unwinding it, so anything half-finished stays half-finished. PHP's own advisory describes the consequence precisely.

“Request memory is temporarily reachable before it is fully initialized. If any allocation fails at that point, PHP does not properly account for the early exit and leaves the request memory in an invalid state.”
— PHP security advisory, GHSA-w8hw-w9fx-j79w

Cleanup then runs over that invalid state and treats it as valid. An attacker who controls when the allocation fails controls what the cleanup walks over. The advisory lists three ways to force the failure on demand: multipart requests that slip past the post_max_size check, unrestricted request headers that exhaust memory_limit, and empty file controls that get around max_file_uploads.

CVE-2026-109619: RCE via stale global state

The second bug needs two halves, and the advisory names them. A producer: some PHP operation stores request-owned state somewhere globally reachable. A consumer: a later operation reads, frees or branches on that state before normal request initialization resets it. Both halves have to land on the same persistent worker, on different requests.

When a fatal error cuts the first request short, the cleanup that would have cleared the global pointer never runs. The pointer survives. The memory it points at does not. Request two picks up a dangling pointer to freed memory, which is a textbook use-after-free, except the attacker gets to shape the heap in between. PHP confirmed two reachable paths: fatal-error backtrace generation, and session upload-progress handling.

Note

The two bugs chain naturally. One corrupts memory at a moment of the attacker's choosing; the other gives a way to read process memory back out, including passwords, authentication tokens, API keys and certificates held by the worker. Memory disclosure first, then code execution against a known layout.

Why this one is worse than a normal critical CVE

Most PHP advisories need something from you. A vulnerable plugin, a specific function, an authenticated session, a file upload form. This one needs a TCP connection.

  1. It runs before your code. On Apache with mod_php, PHP confirmed a working exploit that triggers during request handling, before any application code executes. Your framework's security layer never gets a turn.
  2. It is unauthenticated. The CVSS vector is CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N. PR:N means no privileges required, UI:N means no user interaction.
  3. It does not care what you built. Wiz demonstrated unauthenticated RCE against the latest WordPress, Drupal and Joomla container images. Sansec's list adds Magento 2.4.9, MediaWiki 1.46.2, Roundcube 1.7.4, phpMyAdmin 5.2.3, Nextcloud 35.0.1, Laravel 13.11, Symfony 8.1 and WHMCS.
  4. Hardening can backfire. The advisory explicitly says not to use RLIMIT_AS or RLIMIT_DATA, because they make mmap() fail, which triggers the exact longjmp() path the bug lives on.
  5. The bug is old. Affected releases go back to PHP 5.4, which shipped in 2012. This has been reachable for well over a decade.

That fourth point deserves emphasis, because it inverts normal instinct. If you responded to the news by capping process memory with RLIMIT_AS, you did not harden the server. You made the vulnerable code path easier to reach.

Your exposure depends on how you run PHP

The two advisories are unusually specific about which server APIs are affected and why. The difference comes down to whether the SAPI runs request shutdown after a failed request startup.

How you run PHPBehavior on failed request startupExposure
Apache + mod_phpInvokes request shutdown unconditionally, even when startup failedHighest. PHP demonstrated RCE here before application code runs
FrankenPHPCalls request shutdown unconditionally, analogous to mod_phpHighest, same mechanism as mod_php
PHP-FPMExits the child when request startup failsLower for startup, still susceptible after startup completes
php-cgiListed by PHP as at riskAffected
LiteSpeedListed by PHP as at riskAffected
CLI / cron scriptsNo attacker-controlled HTTP request pathNot the exposed surface, but patch anyway
How each PHP SAPI behaves on the vulnerable path (per GHSA-w8hw-w9fx-j79w)
Watch out

PHP-FPM is safer, not safe. Exiting the child on a failed startup closes one door; the stale-global-state bug still works on a long-lived worker once startup has completed. Treat FPM as a smaller blast radius, not an exemption.

Red Hat scored these at 8.1 Important rather than upstream's 9.2, and its reasoning is worth borrowing as a triage axis even if you disagree with the score. Standard RHEL deployments use PHP-FPM rather than mod_php, and FPM workers exit on early initialization failures before reaching the corrupted structures, which makes remote code execution harder in practice. That matches upstream's own ranking. So patch everything, but if you have to sequence the work, mod_php and FrankenPHP hosts go first.

The number that should worry you is 34.8%

PHP runs 69.3% of all websites whose server-side language is known, measured by W3Techs on October 21, 2026. That figure is doing most of the work in the headlines. The more useful number is the version split underneath it.

PHP branchShare of PHP sitesOfficial fix for php2shell?
PHP 865.2%Only on 8.2, 8.3, 8.4 and 8.5. Nothing for 8.0 or 8.1
PHP 727.0%No. End of life
PHP 57.7%No. End of life
PHP 40.1%No. End of life
PHP version distribution and patch availability (W3Techs, October 21, 2026)

Add up PHP 7, 5 and 4 and you get 34.8% of PHP sites on branches with no patched release. VirusTotal sized the internet-facing footprint on October 21: roughly 5.1 million IP addresses running PHP, about 3.8 million with at least one port open. Only around 1.1 million announce a version in an HTTP header, and among those that do, end-of-life branches outnumber supported ones. About 758,000 on 5.4 through 8.1, against about 272,000 on 8.2 through 8.5.

By the numbers

Roughly four in five internet-facing PHP servers do not advertise their version at all. Every exposure count you read, including this one, is therefore a floor and not a ceiling. VirusTotal is explicit that its figures describe the internet-facing PHP footprint, not confirmed vulnerable hosts, and are the median of seven runs. Published counts elsewhere range from 1.3 million to 5.1 million because they measure different things. If you quote a number, quote its method.

“So many embedded installations out there that will never be patched. Too many forgotten, unmaintained apps.”
— Hal Pomeranz, digital forensics practitioner

This is the part that outlives the news cycle. The patched population moves fast, because hosts and distros push updates and CI rebuilds images. The unpatchable third does not move at all. It becomes permanent attack surface, and most of it is not in anyone's asset inventory.

What to do, in order

The only complete fix is a patched PHP release. Everything else buys time.

Your branchPatched releaseSecurity support endsVerdict
PHP 8.58.5.1231 Dec 2029Patch and you are done
PHP 8.48.4.2731 Dec 2028Patch and you are done
PHP 8.38.3.3631 Dec 2027Patch and you are done
PHP 8.28.2.3531 Dec 2026Patch now, then plan an upgrade. This branch goes EOL in weeks
PHP 8.1 and olderSource backports onlyAlready EOLNo binary release. PHP published minimal source backports for 7.4, 8.0 and 8.1; you must build from source. Upgrading the branch is still the right fix
Patched versions and what they actually buy you
Note

If you are stranded on an end-of-life branch, you are not entirely without options. PHP's security team published minimal source backports for PHP 7.4, 8.0 and 8.1 as branches in the official php-src repository (PHP-7.4-CVE-2026-109618-109619 and the 8.0 and 8.1 equivalents). They are source only, so you have to build them yourself, and they are a bridge rather than a destination. Several vendor summaries state flatly that no fix exists for EOL branches. That is now out of date.

Pro tip

If you patch to 8.2.35 and stop there, note the date: PHP 8.2 security support ends December 31, 2026. You are buying roughly ten weeks, not a year. Budget the 8.3+ migration now while php2shell still has management's attention.

Find your real version and patch state first. Installed packages and the version the running worker reports are not always the same thing, which is exactly how servers get missed.

check-php-version.sh
# What does the RUNNING interpreter report (not just the package)?
php -v
php-fpm -v 2>/dev/null

# Per-SAPI check, because CLI and web can differ
php -r 'echo PHP_VERSION, " ", PHP_SAPI, PHP_EOL;'

# Debian / Ubuntu
dpkg -l 'php*' | awk '$1=="ii"{print $2, $3}'

# RHEL / Alma / Rocky / Amazon Linux
rpm -qa 'php*' --qf '%{NAME} %{VERSION}-%{RELEASE}\n'
dnf module list php --installed

# Containers: the image tag lies more often than the binary
docker ps --format '{{.Names}} {{.Image}}' | while read -r n i; do
  printf '%s\t%s\t' "$n" "$i"
  docker exec "$n" php -r 'echo PHP_VERSION;' 2>/dev/null || echo "no php"
  echo
done
Watch out

A fleet scan that only compares version numbers will flag patched machines as vulnerable. Distributions backport security fixes without bumping the upstream version, so a Debian box reporting 8.2.35-1~deb12u1 or an Ubuntu box on 8.1.2-1ubuntu2.28 is fixed despite the version string. Check the package changelog for the CVE, not just the number: rpm -q --changelog php | grep -i 109618, or zcat /usr/share/doc/php*/changelog.Debian.gz | grep -i 109618. On hosting panels, also check cPanel EA-PHP, Plesk, CloudLinux alt-php and LiteSpeed lsphp builds, because the vulnerable interpreter is often not the one on your PATH.

Then restart the workers. This is the step people skip, and it is the step that decides whether the patch took effect, because a long-running worker keeps serving the old code in memory.

patch-and-restart.sh
# Patch, then RESTART. A patched package with an old worker is still vulnerable.
# Debian / Ubuntu
sudo apt update && sudo apt install --only-upgrade 'php*'
sudo systemctl restart php8.3-fpm        # match your version
sudo systemctl restart apache2           # if you use mod_php

# RHEL / Alma / Rocky
sudo dnf update 'php*'
sudo systemctl restart php-fpm
sudo systemctl restart httpd             # if you use mod_php

# Stuck on an EOL module stream (AlmaLinux 8 example, per AlmaLinux advisory)
sudo dnf module reset php
sudo dnf module enable php:8.2
sudo dnf distro-sync 'php*'

# Confirm the running worker, not the package, is patched
php -v && systemctl show -p ActiveEnterTimestamp php-fpm
By the numbers

PHP's advisory calls patching "the only complete mitigation". A PHP core developer, Gina Peter Banyard, stated publicly that no php2shell mitigation fully blocks potential attacks. Treat every config tweak below as a stopgap you remove later, not a fix you keep.

One thing a WAF cannot do is save you here, and the WAF vendors say so themselves. Cloudflare calls its rules defence in depth and reports attackers mutating payloads to evade them. Sansec is blunter: the RCE and memory-disclosure paths run inside PHP, before your store code and before any Shield rule, so do not wait for a WAF rule to protect you. The same architecture explains why a WordPress security plugin cannot help. PHP parses the request before any plugin code runs.

If you genuinely cannot patch today

These reduce specific attack paths. None of them makes an installation safe, and the advisories say so directly.

  • Set memory_limit=-1 to remove the predictable per-request limit the OOM trigger relies on. Understand the tradeoff: a runaway request can now consume host memory.
  • Enforce a conservative post_max_size at the edge, and block multipart requests entirely if your application does not use uploads.
  • Enforce decoded request size, field count, multipart part count, header count and nesting depth at the proxy. PHP's advisory stresses these limits must also cover requests without a usable Content-Length, because the confirmed trigger uses multipart requests with no declared length to slip past post_max_size.
  • Reduce max_input_nesting_level.
  • Set fatal_error_backtraces=0 and session.upload_progress.enabled=0. Both close confirmed paths for CVE-2026-109619 only. Partial, not complete.
  • Do NOT set RLIMIT_AS or RLIMIT_DATA. Per the advisory these cause mmap() to fail, which triggers the vulnerable longjmp() path.
  • Restart every PHP worker after any of the above. A php.ini change without a restart changes nothing for running workers.
Watch out

The single most repeated piece of mitigation advice online is wrong about which bug it fixes. Alexandre Daubois, writing from inside the PHP security team, notes that session.upload_progress.enabled = Off "belongs to the other bug" and that "the CVE-2026-109618 advisory never mentions sessions". Turning it off does nothing for the OOM-corruption bug. Do not let it convince anyone the OOM path is handled.

The UK's NCSC went further than usual in its October 21 guidance, telling organisations to consider taking systems offline if they cannot mitigate quickly, and to assess systems for compromise before upgrading. That second instruction matters: patching destroys the evidence of a pre-patch intrusion.

Is it being exploited? The honest answer is "partly disputed"

Coverage of this has been louder than the evidence. Here is what each organisation actually reported on October 21, with times, so you can judge it yourself.

SourceReportReads as
Cloudflare (CTO Dane Knecht)Observing "early attacks"; WAF protections live 03:02 UTC, ~9h before disclosureAttack traffic seen
PatchstackHosting partners reported active exploitation incidents at 07:34 UTCAttack traffic seen
Patchstack telemetry, 13:57 UTC"Only probing attacks, with no hard evidence of breaches yet"Probing, not confirmed compromise
ImpervaObserved no traffic matching monitored php2shell exploit patterns pre-disclosureNo attack traffic seen
watchTowr and HuntressHad not seen either vulnerability exploited in the wildNo attack traffic seen
CISA KEV catalogHad not listed either CVE as of October 21Not formally confirmed exploited
Who reported what about in-the-wild exploitation, October 21, 2026

Those look contradictory. They are not, and the distinction is the most useful thing in this section. Imperva's statement covers the pre-disclosure monitoring window, before the October 20 leak, when it had been given signatures confidentially and saw nothing. Cloudflare's report is from the morning of October 21, after the leak. Huntress timestamped its negative precisely, at 10am ET on October 21. Read in order, the sources tell one coherent story rather than three competing ones: quiet before the leak, scanning and exploitation attempts within hours of it, and no published evidence of a confirmed compromise on day one.

We checked the last line independently. CISA's Known Exploited Vulnerabilities catalog, version 2026.10.21 with 1,754 entries, contained neither CVE. Note also what the positive reports actually say: attempts and attack traffic, not confirmed compromise. watchTowr reproduced CVE-2026-109618 in a lab, which is a statement about the bug being real, not about victims.

Watch out

Treat "no confirmed mass exploitation on day one" as a scheduling detail, not reassurance. Sansec predicted mass exploitation would begin within roughly 15 minutes of the patch release, because the patch itself is a map to the bug. The window you are patching against closed when the fix shipped.

Two claims to handle carefully if you are briefing leadership. The widely quoted line about mass attacks beginning within 15 minutes of release is a prediction from Sansec, not a measurement, and it has been repeated as though it were an observation. And reporting on watchTowr has been inconsistent: it was quoted in SecurityWeek saying it had not seen exploitation in the wild, while its own later intelligence pages describe observing exploitation attempts through its honeypot network. Those are different claims made at different times. Cite the primary statement with its timestamp, not the aggregated summary.

The AI story is real, but it is not the one in the headlines

php2shell was disclosed jointly by Wiz Research (Sagi Tzadik, Shir Tamari, Nir Ohfeld and Tomer Schwartz), OpenAI's Codex Security team (Amy Burnett) and Calif (Ryan Austin). The GHSA advisories credit lcfr-hax, sagitzadik-wiz, tomash-areion and amyb-oai as reporters, with arnaud-lb, iluuu1994, bukka and ndossche building the fix.

Wiz says it used Wiz Atlas, Gemini 4 Argon and GPT-5.6 Sol, and credits Gemini 4 Argon with "an invaluable contribution to finding the vulnerability, exploit development, attack detection, and validation of the patch". It frames php2shell as an inflection point and reports that "a single researcher completed in a week what previously would have taken a world-class team months of work". That is a striking claim, and it is worth reading carefully rather than repeating.

Humans found this bug class in 2019. It is in PHP's own changelog.

The "AI found what humans could not" reading does not survive contact with PHP's own records. Check the PHP 7 changelog for version 7.3.18, released May 14, 2020:

php.net/ChangeLog-7.php, Version 7.3.18
Fixed bug #78875 (Long filenames cause OOM and temp files are not cleaned). (CVE-2019-11048)
Fixed bug #78876 (Long variables in multipart/form-data cause OOM and temp files
                  are not cleaned). (CVE-2019-11048)

That is the same primitive: an out-of-memory condition during multipart parsing that skips cleanup. It was reported in 2019, assigned CVE-2019-11048 and partially patched in 2020. The PHP Foundation's own account goes further, describing how the current bug surfaced during unrelated work: "While fixing one of the issues Quarkslab reported, a PHP core developer contracted by the PHP Foundation found the first signs of the OOM problem behind CVE-2026-109618." A human found it, during a human audit.

So why did it sit? The Foundation answers that directly, and the honesty is unusual.

“We won't pretend otherwise: the delay was a structural failure, caused by both the security team's decision process and its lack of resources.”
— The PHP Foundation, October 21, 2026

The Foundation explains that several competing fixes existed, but "each came with its own trade-offs: backward compatibility breaks, new denial-of-service potential, or requiring changes to many third-party extensions", and that the team "struggled to choose between these and feared the repercussions of shipping the wrong choice". The eventual fix shows why. Arnaud Le Blanc's commit, titled "Avoid state corruption via bailout", replaces the OOM bailout with UnwindExit and touches 300 files with 10,759 insertions. That is not a patch anyone ships casually.

What actually changed was the price of a working exploit

Calif's Ryan Austin, who reported the bug before Wiz, is explicit that the discovery was human-steered, not model-driven. He says no special harness or privileged model access was used in the initial discovery, and explains why the models do not get there alone:

“LLMs may have ingested public historical vuln data, but in their default variant analysis tends to move from the newest and most prominent bugs toward nearby older code. They do not reliably begin with obscure twenty-year-old exploitation primitives.”
— Ryan Austin, Calif

Put the pieces together and a more defensible story emerges, one that happens to be the story Wiz also tells when you read past the headline. Wiz's own words are that this "changes the economics of both discovery and exploitation" and that vulnerabilities "that once seemed impractical to exploit become viable targets". The bug was known. The exploit was the expensive part: weeks of heap grooming and offset tuning per target. That cost was the only thing protecting a third of the web, and it is the thing that collapsed.

Key takeaway

The lesson is not that AI finds bugs humans cannot. It is that "theoretically vulnerable but too tedious to exploit" has stopped being a security control. If your risk model quietly relies on exploitation being hard, it is now out of date, and reachability and patch latency matter more than raw vulnerability counts.

For calibration, DARPA's AI Cyber Challenge published hard numbers on this in August 2025: competing systems found 18 real zero-days across C and Java codebases and validly patched 11 of them, at an average of $152 and 45 minutes per task. Google's Big Sleep had already found a real SQLite flaw, CVE-2025-6965, and Google said it was the first time an AI agent directly disrupted exploitation in the wild. The trend is well documented. php2shell is the moment it reached infrastructure that most of the web runs on.

One more caution for anyone briefing this upward. Wiz describes the affected range as going back to PHP 5.4, while Daubois points out that "the advisory lists patched versions for 8.2 through 8.5 only". Both are defensible depending on whether you mean "contains the bug" or "has a fix available", which is exactly the kind of ambiguity that turns into a wrong slide. Say which one you mean.

Detection: what to look for in your logs

No vendor has published a reliable post-compromise signature for this yet, so detection splits into two jobs. Finding exploitation attempts, and finding the fallout if one worked.

Attempt indicators

Vendor WAF descriptions converge on the same request shapes, because the bug needs memory exhaustion to be triggered on demand.

  • Unusually large multipart payloads, and requests with excessive multipart field counts.
  • Abnormally large or numerous request headers, consistent with exhausting memory_limit.
  • Anomalous session upload-progress fields in POST bodies.
  • Unexpectedly large PHPSESSID values in Set-Cookie response headers, which Imperva calls out as a response-side signal.
  • A spike in PHP fatal errors, specifically "Allowed memory size of N bytes exhausted", from internet-facing endpoints that have no business running out of memory.

Cloudflare's approach is worth copying conceptually: it inspects the request and the response in the same transaction, because the response is where memory disclosure shows up. Request-only rules miss the half of the attack that leaks data back.

detect-attempts.sh
# Spike in OOM fatals is the cheapest attempt signal you have
sudo grep -h 'Allowed memory size' /var/log/php*.log /var/log/php-fpm/*.log 2>/dev/null \
  | awk '{print $1, $2}' | sort | uniq -c | sort -rn | head -20

# Oversized POSTs to endpoints that should never receive them (nginx/apache combined)
sudo awk '$NF ~ /^[0-9]+$/ && $NF > 1000000 && $6 ~ /POST/ {print $1, $7, $NF}' \
  /var/log/nginx/access.log | sort -k3 -rn | head -20

# Suspiciously long Set-Cookie / PHPSESSID values (memory disclosure tell)
sudo grep -ho 'PHPSESSID=[^;" ]*' /var/log/nginx/access.log 2>/dev/null \
  | awk '{ if (length($0) > 60) print length($0), $0 }' | sort -rn | head

Post-compromise indicators

Successful RCE lands as the web server user, so look where that user can write and what it should never do.

hunt-post-compromise.sh
# Files written into the webroot recently (classic webshell drop)
sudo find /var/www -type f \( -name '*.php' -o -name '*.phar' \) -mtime -7 -ls

# PHP files in upload/cache dirs, which should never contain executable PHP
sudo find /var/www -path '*upload*' -o -path '*cache*' -o -path '*tmp*' \
  | xargs -r -I{} find {} -name '*.php' -type f 2>/dev/null

# Unexpected children of the PHP worker: shells, curl, package managers
ps -eo pid,ppid,user,etimes,args --forest | grep -E 'php-fpm|httpd|apache2' -A3 \
  | grep -E '(sh|bash|curl|wget|python|perl|nc)\b'

# Outbound connections from the web server user
sudo ss -tnp 2>/dev/null | grep -E 'php|httpd|apache' 

# Cron and systemd persistence added as the web user
sudo crontab -l -u www-data 2>/dev/null; sudo crontab -l -u apache 2>/dev/null
systemctl list-timers --all | tail -n +2 | head -20
Note

Collect this evidence before you patch where you can. NCSC's guidance explicitly says to assess systems for compromise before upgrading. Patching replaces the binary and restarts the workers, which is also how you lose the process state and in-memory artifacts an investigation would want.

The uncomfortable lesson: this was an inventory problem

Nothing in your dependency manifest mentions the PHP interpreter. It is not an npm package or a Composer requirement. It is the runtime underneath all of them, which is exactly why so many teams spent October 21 asking a question they could not answer quickly: where does PHP run here, in what version, behind which SAPI?

Teams that answered in minutes had a current inventory that covered runtimes and base images, not just application dependencies. Teams that answered in days went server by server. The vulnerability was identical for both groups. The exposure window was not.

That is the durable takeaway from php2shell, and it is independent of PHP. The next engine-level CVE, in a language runtime, a TLS library or a container base layer, will ask the same question. Your answer time is a security control, and you can measure it today.

Frequently asked questions

What is php2shell?

+
php2shell is the common name for two critical PHP interpreter vulnerabilities, CVE-2026-109618 (RCE via out-of-memory corruption) and CVE-2026-109619 (RCE via stale global state). Both are rated CVSS v4.0 9.2 and allow unauthenticated remote code execution. PHP released fixes on October 21, 2026.

Which PHP versions are affected by CVE-2026-109618 and CVE-2026-109619?

+
Affected releases go back to PHP 5.4. Fixed versions are PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. PHP 8.1 and every earlier branch are end of life and receive no official fix, so those installations need a branch upgrade rather than a patch.

Can I mitigate php2shell without patching?

+
No. PHP's advisory states that updating is the only complete mitigation, and a PHP core developer confirmed no config change fully blocks attacks. Settings like memory_limit=-1, fatal_error_backtraces=0 and session.upload_progress.enabled=0 reduce specific attack paths only. Always restart PHP workers after any change.

Why should I avoid RLIMIT_AS when hardening against php2shell?

+
Because it makes the vulnerability easier to trigger. PHP's advisory warns against RLIMIT_AS and RLIMIT_DATA since they cause mmap() to fail, which is precisely what drives PHP down the vulnerable longjmp() out-of-memory path. Use post_max_size limits at the edge instead.

Is php2shell being exploited in the wild?

+
The evidence is mixed. Cloudflare and Patchstack reported early attacks and probing on October 21, while Imperva, watchTowr and Huntress said they had seen no exploitation, and CISA's KEV catalog had not listed either CVE that day. Probing was real; widespread confirmed compromise was not established on day one.

Am I safe if I use PHP-FPM instead of Apache mod_php?

+
You have a smaller blast radius, not immunity. PHP-FPM exits the child process when request startup fails, which closes one path, but it remains susceptible after startup completes and to the stale-global-state bug on long-lived workers. Apache mod_php and FrankenPHP are the highest-risk configurations.

Does patching to PHP 8.2.35 solve this long term?

+
It fixes php2shell, but PHP 8.2 security support ends December 31, 2026. Patching to 8.2.35 buys roughly ten weeks before that branch stops receiving security fixes entirely. Plan a move to PHP 8.3 or later as part of the same remediation effort.

Did AI discover the php2shell vulnerabilities?

+
Partly. Wiz credits Gemini 4 Argon with a major contribution, but the same OOM-cleanup bug class was reported by humans in 2019 as CVE-2019-11048, and Calif's Ryan Austin says the initial discovery was human-steered with no special model access. AI's decisive contribution was making a known bug practical to exploit.

Is there a patch for PHP 7.4, 8.0 or 8.1?

+
There is no binary release, but PHP's security team published minimal source backports for 7.4, 8.0 and 8.1 as branches in the official php-src repository, so you can build a fixed interpreter yourself. Many vendor summaries incorrectly state that no fix exists for end-of-life branches. Upgrading to a supported branch remains the real fix.

Sources

  1. https://www.php.net/
  2. https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w
  3. https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp
  4. https://www.php.net/supported-versions.php
  5. https://www.wiz.io/blog/php2shell-php-rce
  6. https://w3techs.com/technologies/details/pl-php
  7. https://www.ncsc.gov.uk/news/critical-security-issue-affecting-php
  8. https://blog.cloudflare.com/php-vulnerabilities-waf-protection
  9. https://sansec.io/research/php2shell
  10. https://www.imperva.com/blog/php2shell-vulnerability-updates/
  11. https://cloud.google.com/blog/products/identity-security/responding-to-core-php-vulnerabilities-cve-2026-109618-cve-2026-109619
  12. https://almalinux.org/blog/2026-10-21-php2shell-cve-2026-109618-cve-2026-109619/
  13. https://patchstack.com/articles/php-major-vulnerability-alert-10-2026/
  14. https://www.infosecurity-magazine.com/news/critical-php-vulnerabilities-leaked/
  15. https://www.helpnetsecurity.com/2026/10/21/php2shell-php-vulnerabilities-patched-cve-2026-109618-cve-2026-109619/
  16. https://www.securityweek.com/php2shell-mass-exploitation-looms-for-leaked-critical-php-flaws/
  17. https://github.com/php/php-src/commit/01a40e89fbfc499936e3b05888d8f00bfde886df
  18. https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/
  19. https://www.php.net/ChangeLog-7.php
  20. https://daubois.dev/blog/cve-2026-109618-php2shell-rumors/
  21. https://deadneurons.substack.com/p/php2shell-explained-php2shell-was
  22. https://www.darpa.mil/news/2025/aixcc-results
  23. https://blog.virustotal.com/2026/10/sizing-php-exposure-window-for-cve-2026.html
  24. https://www.huntress.com/blog/php2shell-leaked-critical-php-vulnerabilities
  25. https://access.redhat.com/security/cve/CVE-2026-109618
  26. https://ubuntu.com/security/CVE-2026-109618
  27. https://security-tracker.debian.org/tracker/CVE-2026-109618

See your full attack chain.
Code, build, runtime. One platform.