PHP2Shell: What Provides Protection and Why Unpatched Websites Will Remain

Contents
- Updated situation: attempts before public disclosure
- What PHP2Shell means
- Which updates provide protection?
- From updated packages to protected services
- The consequences of a missing update
- Will administrators actually install the updates?
- How many websites will remain at risk?
- Linux operations: inventory the runtime actually in use
- Updating PHP 8.4 with APT
- Updating PHP 8.5 with APT
- Updated packages do not prove updated running processes
- PHP 7.4: a security backport instead of unchanged 7.4.33
- Investigation: correlate multiple signals
- Recognizing PHP2Shell in logs: suspicion is not proof
- Initial Linux log queries
- Improve logging without collecting form bodies
- Protection while an update is pending
- Concrete intrusion indicators: separate patching from incident response
- Questions and answers
- Sources
As of 22 October 2026. PHP2Shell affects the runtime beneath a large part of the web. Two critical vulnerabilities can allow unauthenticated remote code execution under suitable conditions. Security updates have been available since 21 October. The practical question is now which internet-facing installations are actually running corrected PHP. [1] [6]
Updated situation: attempts before public disclosure
AWS reports a PHP2Shell exploitation attempt on 17 October 2026 in its own MadPot telemetry, before public disclosure. For additional observed attempts, success against real-world systems remained uncertain. This is documented warning evidence, not a global count of compromised systems. Investigations of potentially affected installations should therefore reach back before the publication date. AWS: first-hand observations and limitations
What PHP2Shell means
The identifiers are CVE-2026-109618 and CVE-2026-109619. The first involves invalid internal memory state following an out-of-memory condition. The second involves internal references surviving after the underlying memory has been freed. Subsequent processing can turn either condition into a security failure. Both flaws are inside PHP itself. [3] [4]
“Every version since PHP 5” conveys the exceptional reach, but does not replace package verification. Old PHP 5 and 7 branches require scrutiny alongside PHP 8. cPanel lists older packages and specific corrected vendor revisions. Moving from PHP 7 to an arbitrary PHP 8 release is therefore insufficient evidence of protection. [7]
Apache with mod_php presents a particularly serious case: a demonstrated attack path operates before application code starts. PHP-FPM is not a safe exemption; the PHP Foundation also reports confirmed code execution through application-dependent paths. A WordPress login cannot reliably protect against a flaw below the application layer. [2]
Which updates provide protection?
The corrected official releases are 8.2.35, 8.3.36, 8.4.27 and 8.5.12, or a later corrected release in the corresponding branch. PHP 8.1 and earlier no longer receive regular PHP-project releases. Exceptional minimal source backports exist for 7.4, 8.0 and 8.1, while distributions and LTS vendors may supply corrected packages. [1]
An old base version does not necessarily mean an absent patch. The complete package revision and confirmation covering both CVEs matter. Conversely, a “PHP 8” label establishes no protection. [7]

From updated packages to protected services
- Inventory: Domains, PHP pools, containers, forgotten test installations and embedded administration tools all belong in scope.
- Evidence: Each runtime needs a recorded package source, revision and confirmation that both CVEs are addressed.
- Running instances: PHP workers must load the corrected build. Custom containers require an updated image and redeployment; updating the host does not modify an existing container image. [8]
- Web verification: The command line may use a different PHP installation from the website. Mapping each domain to the active runtime, followed by functional checks, completes the operational verification.
Where immediate patching remains impossible, ENISA recommends considering isolation of exposed applications. A static maintenance page served before PHP can stop requests reaching the vulnerable runtime. A maintenance plugin running inside that same PHP application does not reliably provide that separation. [5]
Official configuration mitigations only offer temporary reductions in exposure. For example, session.upload_progress.enabled=0 blocks specific confirmed session paths, not every possible variant. Changes to memory limits are not a universal repair either. Unlimited PHP memory can introduce availability risks; appropriate limits outside PHP require careful coordination. None of these measures replaces patching. [3] [4]
The consequences of a missing update
The starting point is code execution with the web worker account’s permissions, not automatic root access. Depending on permissions and accessible secrets, consequences can include stolen database credentials, exposed customer records, altered content, planted malware and abused API keys. Reachable internal services may become further targets where network access and credentials permit. [3]
For an online shop, a modified payment page can be more damaging than an obvious outage. Investigation, recovery, downtime and loss of trust add further costs. A later patch does not remove an existing backdoor. Concrete intrusion indicators call for isolation, evidence preservation, credential review and recovery from a trusted state.
Will administrators actually install the updates?
Assessment: A rapid initial reduction in exposed installations is plausible; complete coverage within days is not. Centrally operated platforms can update large numbers of websites together. Individual servers depend on ownership, package availability, compatibility and controlled restarts. Google Cloud explicitly distinguishes automatically updated runtimes from services requiring redeployment. [8]
The remaining population is likely to be persistent: abandoned projects, old agency deployments, containers without maintenance processes and applications tied to unsupported branches. Delays do not automatically indicate administrator negligence. Missing budgets, unclear responsibilities and untested legacy dependencies can obstruct a technically straightforward patch. A specific global update rate would currently be speculation.
How many websites will remain at risk?
On 22 October, W3Techs reports PHP on 69.3% of websites with a known server-side programming language. Within the detected PHP population, PHP 7 accounts for 26.9% and PHP 5 for 7.7%, totaling 34.6%. These figures describe adoption and legacy usage, not measured unpatched exposure. [9]
No reliable absolute remainder follows from those figures. Vendor backports, configuration differences, hidden version information and multiple websites sharing a server prevent direct conversion. Individual web pages, domains and servers also represent different counting units.
A purely illustrative calculation: in a population of one million genuinely affected installations, a successful 95% update rate would leave 50,000 unpatched. This is neither a measurement nor a forecast. The arithmetic simply explains why even a high completion rate can leave a substantial remainder at internet scale. Verified patch status matters more for a particular website than a global percentage.
Linux operations: inventory the runtime actually in use
The following package examples apply to Debian/Ubuntu systems using APT with existing trusted repositories. The relevant PHP branch must already be installed. RPM systems, hosting panels and custom containers require the corresponding vendor workflow. Adding an arbitrary third-party repository is not a reliable repair strategy.
A recoverable backup, a mapping of domains to PHP pools and a short functional test plan belong before the change. Package installation may restart services itself, so the maintenance window starts before the installation command. This inventory covers the CLI and services, but does not yet establish the web handler:
cat /etc/os-release
command -v php
php -v
php --ini
dpkg-query -W -f='${binary:Package}\t${Version}\n' 'php*' 'libapache2-mod-php*' 2>/dev/null
systemctl list-units --type=service --all | grep -E 'php.*fpm|apache2|nginx'
ps -eo pid,lstart,comm | grep -E 'php|apache2'A domain can use PHP 8.4 through FPM while the shell command php points to 8.5. Additional copies can exist in containers, hosting directories and forgotten test services. Evidence for each installation needs the package source, complete revision, service or container and associated websites.
Updating PHP 8.4 with APT
This workflow only upgrades installed 8.4 packages, including existing extensions and any installed Apache module. Simulation changes no packages. The candidate must contain PHP 8.4.27 or a vendor-confirmed backport addressing both CVEs. Debian 13 documents 8.4.27-1~deb13u1 as fixed. APT documentation · Debian: CVE-2026-109618 · Debian: CVE-2026-109619
set -euo pipefail
branch=8.4
sudo apt-get update
mapfile -t php_packages < <(
dpkg-query -W -f='${db:Status-Abbrev} ${binary:Package}\n' \
"php${branch}-*" "libapache2-mod-php${branch}" 2>/dev/null |
awk '$1 == "ii" { print $2 }'
)
test "${#php_packages[@]}" -gt 0
apt-cache policy "${php_packages[@]}"
sudo apt-get -s install --only-upgrade "${php_packages[@]}"Installation follows review of the plan. Unexpected removals, branch changes or vulnerable candidates are reasons to stop. The next block belongs in the same Bash session and assumes an existing FPM service:
sudo apt-get install --only-upgrade "${php_packages[@]}"
sudo /usr/sbin/php-fpm8.4 -t
sudo systemctl restart php8.4-fpm
systemctl is-active php8.4-fpm
/usr/bin/php8.4 -v
/usr/sbin/php-fpm8.4 -vUpdating PHP 8.5 with APT
An existing 8.5 deployment uses the same workflow with a corrected candidate starting at 8.5.12 or a confirmed vendor backport. Debian 13’s standard repository does not provide PHP 8.5 simply because a newer PHP branch exists. Without a suitable candidate from the already approved source, this workflow stops before installation.
set -euo pipefail
branch=8.5
sudo apt-get update
mapfile -t php_packages < <(
dpkg-query -W -f='${db:Status-Abbrev} ${binary:Package}\n' \
"php${branch}-*" "libapache2-mod-php${branch}" 2>/dev/null |
awk '$1 == "ii" { print $2 }'
)
test "${#php_packages[@]}" -gt 0
apt-cache policy "${php_packages[@]}"
sudo apt-get -s install --only-upgrade "${php_packages[@]}"Continuation again requires a reviewed plan, the same Bash session and an existing FPM service.
sudo apt-get install --only-upgrade "${php_packages[@]}"
sudo /usr/sbin/php-fpm8.5 -t
sudo systemctl restart php8.5-fpm
systemctl is-active php8.5-fpm
/usr/bin/php8.5 -v
/usr/sbin/php-fpm8.5 -vApache with mod_php instead requires an updated Apache module and an orderly Apache restart after a successful configuration check. On Debian the check is sudo apache2ctl configtest. A PHP package update in a pure FPM setup does not require a blanket nginx restart. Separate FPM masters, persistent CLI services and containers need individual treatment. Restarts can interrupt active requests; redundant deployments should be rolled out sequentially.
Updated packages do not prove updated running processes
Linux can keep a process running from an old executable after replacement. A fresh php -v then reports the corrected release while an existing worker continues with the old build. This read-only check identifies PHP processes retaining a deleted executable:
sudo bash <<'BASH'
for link in /proc/[0-9]*/exe; do
target=$(readlink "$link" 2>/dev/null) || continue
case "$target" in
*php*"(deleted)"*)
pid=${link#/proc/}
pid=${pid%/exe}
printf 'PID %s: %s\n' "$pid" "$target"
;;
esac
done
BASHA match calls for version and service verification, not an automatic intrusion diagnosis. An empty result does not establish complete protection either: loaded Apache modules, libraries and separately embedded runtimes need their own checks. Active workers matter more than package listings. Login, uploads, forms, queues and scheduled jobs belong in application-specific functional checks. Public phpinfo() pages are unsuitable for this task; protected internal diagnostics or active-process mapping are preferable.
PHP 7.4: a security backport instead of unchanged 7.4.33
PHP 7.4 remains end-of-life. Migration to a supported branch or a verified LTS patch from the existing supplier is preferable. For an unavoidable transition period, the PHP project published a minimal source backport covering both PHP2Shell identifiers. This does not restore general support or promise fixes for unrelated vulnerabilities.
The security commit verified on 22 October is 712e60f9edfd03f0960fc4fc3c2c375f42b90aa2. The workflow pins that exact source revision. Rebuilding the unchanged 7.4.33 release does not include this correction. PHP: specific security commit
The example produces a minimal CLI/FPM build in a separate compatible Debian/Ubuntu build environment with Git and CA certificates already available. All blocks run in the same Bash session. This is not an installation executed or fully validated on a production server for this article. Old PHP sources are not compatible with arbitrary current system libraries.
set -euo pipefail
workdir=$(mktemp -d "$HOME/php74-php2shell.XXXXXX")
cd "$workdir"
git init php-src
cd php-src
git remote add origin https://github.com/php/php-src.git
commit=712e60f9edfd03f0960fc4fc3c2c375f42b90aa2
git fetch --depth=1 origin "$commit"
git checkout --detach FETCH_HEAD
test "$(git rev-parse HEAD)" = "$commit"
git show -s --format='%H%n%s%n%b' HEADThe minimum tools and isolated installation prefix follow below. --disable-all keeps the example small; the result does not automatically include every extension required by WordPress or another application. The configured user and group www-data must exist in the target environment. PHP: Unix installation · PHP: FPM build options
sudo apt-get update
sudo apt-get install --no-install-recommends \
build-essential autoconf bison re2c pkg-config \
libxml2-dev libsqlite3-dev
./buildconf --force
prefix=/opt/php74-php2shell-712e60f9
test ! -e "$prefix"
./configure \
--prefix="$prefix" \
--with-config-file-path="$prefix/etc" \
--with-config-file-scan-dir="$prefix/etc/conf.d" \
--disable-all --enable-cli --enable-fpm --enable-session \
--with-fpm-user=www-data --with-fpm-group=www-data \
--without-pear
make -j2
NO_INTERACTION=1 REPORT_EXIT_STATUS=1 make testFailed tests or unexplained skips of relevant tests need investigation. Installation into the new prefix follows an accepted test run; this does not replace /usr/bin/php or distribution packages:
sudo make install
sudo install -d -m 0755 "$prefix/etc/conf.d"
sudo install -m 0644 php.ini-production "$prefix/etc/php.ini"
"$prefix/bin/php" -n -v
"$prefix/bin/php" --ini
"$prefix/sbin/php-fpm" -v
git rev-parse HEADThe output can still carry a 7.4 version number. Evidence of the fix consists of the verified source commit, build records and the artifact actually running. Production use still requires the necessary extensions and their tests, such as database drivers, mbstring, XML, cURL or ZIP. Development libraries, ABI and phpize/php-config must match this specific build.
FPM configuration, pool accounts and filesystem permissions then require separate review. A dedicated socket and service prevent accidental mixing with the existing runtime. Only after a successful configuration test using the new php-fpm -t -y /path/to/php-fpm.conf does the web handler switch to that pool. A successful CLI command changes no website. If rollout fails, fallback must not silently restore public access to a vulnerable runtime; isolation remains the interim option.
Investigation: correlate multiple signals
Datadog reproduced the flaws in its laboratory. Successful code execution need not crash a worker or produce a suspicious HTTP status. HTTP 200 therefore provides no assurance. Suspicious form submissions, memory errors and unexpected child processes become meaningful through correlation. A single memory-exhaustion error alone does not prove an attack. Datadog Security Labs: original analysis
Investigation combines web-server/proxy logs, PHP errors, process events and unexpected file changes. Existing evidence should be preserved before cleanup. Indiscriminate recording of complete form bodies would introduce passwords and personal data into logs; metadata and controlled forensic collection are preferable. A WAF can constrain requests but cannot fix PHP. Completion needs two distinct findings: a corrected runtime is active, and possible prior compromise has been investigated.
Recognizing PHP2Shell in logs: suspicion is not proof
No universal “PHP2Shell detected” log entry exists. Investigation must distinguish an unusual request, a technical failure and confirmed unauthorized code execution. A useful chain connects an unusual proxy request, an error or process event in the relevant PHP worker and a subsequent action unexplained by normal operation. Missing records can also result from disabled logging, rotation or short retention.
| Observation | Interpretation | Next check |
|---|---|---|
| Many POST requests or large uploads | Also normal for imports and legitimate uploads | Target route, time and deviation from the usual workload |
| Memory exhaustion, worker termination or segfault | A lead, not a unique PHP2Shell signature | Related requests, pool, PID and known software defects |
| Unexpected shell or download tool as a child process | Higher priority; some applications legitimately launch such programs | Parent process, actual purpose and timing |
| Unauthorized code change or proven foreign command | Concrete evidence suggesting compromise | Isolation, evidence preservation and scope assessment |
Initial Linux log queries
The following commands read existing data. Service names, paths and time range must match the installation. The timestamp uses the local system timezone; cross-system correlation requires accounting for offsets and clock synchronization. PHP errors appear in the journal only when the service actually sends those messages there. systemd: journalctl
sudo journalctl --since '2026-10-17 00:00:00' \
-u php8.4-fpm -u php8.5-fpm -u apache2 -u nginx \
--no-pager -o short-iso |
grep -Ei 'Allowed memory size.*exhausted|Out of memory|segfault|SIGSEGV|child .*exited on signal'No output means only “no match in these sources with this pattern.” Separate FPM error files, container logs and virtual-host-specific logs require additional queries. Rotated files also belong in the investigation window.
For common Apache/nginx access logs recording the HTTP request, this query returns the last hundred matching POST lines from the files read. This is initial triage, not a complete scanner; file ordering does not guarantee chronological sorting:
sudo zgrep -hE '"POST [^"]+ HTTP/[0-9.]+"' \
/var/log/nginx/access.log* /var/log/apache2/access.log* |
tail -n 100Missing paths or different log formats require adjusted file selection. Standard access logs normally omit the complete request body. A commonly recorded byte count usually describes the response, not the upload. Searching ordinary access logs for PHP_SESSION_UPLOAD_PROGRESS may therefore find nothing even when that form field was sent. Missing body data cannot be reconstructed retrospectively. Apache: log meanings and configuration
A snapshot of process relationships can provide additional context:
ps -eo pid,ppid,lstart,user,comm --forestThis lists only processes currently running. Completed commands appear only in previously enabled execution monitoring, such as audit or EDR telemetry. Monitoring enabled today provides no retrospective execution history.
Improve logging without collecting form bodies
An additional nginx log can correlate request ID, time, method, path, status, request size and duration. The following proposed format belongs in the http context. It helps future investigations; previously missing data remains missing. nginx: log_format and access_log
log_format php2shell escape=json
'{"time":"$time_iso8601","request_id":"$request_id",'
'"client":"$remote_addr","method":"$request_method","uri":"$uri",'
'"status":"$status","request_bytes":"$request_length",'
'"response_bytes":"$body_bytes_sent","duration":"$request_time",'
'"upstream_status":"$upstream_status"}';The next directive enables that format in the selected server context. Existing logs and inheritance need review so the new directive does not silently displace required logging.
access_log /var/log/nginx/php2shell-access.log php2shell;A successful nginx -t check precedes an orderly reload. request_bytes covers the complete received request including headers, not just file size or resulting PHP memory consumption. The format excludes query strings, cookies, authorization headers and bodies. IP addresses and paths can still contain personal information; access controls, retention and rotation belong in the setup. Behind a proxy, the client address is reliable only with a correctly configured trust chain. OWASP: secure logging
Protection while an update is pending
The corrected runtime takes priority. Additional controls restrict exposure and potential consequences but do not replace patching. For an exposed application that cannot be updated promptly, isolation before PHP is the clearest interim measure: a static maintenance page, VPN or upstream access control. Direct origin access must enforce the same boundaries; a protected CDN path with an openly reachable backend is insufficient.
- Request limits: Size, upload and rate limits at the reverse proxy should match actual needs. Legitimate imports and large files require tested rules. A small request can consume more memory internally, so size limits are not complete protection.
- WAF as another layer: Appropriate vendor rules require activation and enforcement checks. Observation-only rules block nothing; an HTTP 403 only establishes rejection of that particular request.
- Restricted privileges: Separate pool accounts, minimal file permissions, restricted database accounts and limited network access reduce potential damage. A compromised application can still abuse every secret legitimately available to that application.
- Official interim measures: Disabling session upload progress early blocks particular paths, not both flaws completely. Blanket removal of PHP memory limits without suitable external boundaries can create further availability failures.
Concrete intrusion indicators: separate patching from incident response
Deleting a suspicious file and updating PHP is insufficient for a defensible closure. The affected instance first needs isolation against further access. Relevant logs, timestamps and available process evidence should be preserved alongside containment. Unconsidered cleanup can destroy useful evidence.
Subsequent steps include assessing accessible data and adjacent systems, replacing potentially exposed credentials from a trusted environment and rebuilding from verified sources where appropriate. A backup must predate possible compromise both chronologically and in content. Returning to public service requires a corrected active runtime, checked application and continued monitoring. “Patch installed” and “incident investigated” remain separate findings.
Questions and answers
Is a WordPress update sufficient?
No. Updating WordPress does not replace a corrected PHP runtime. Application updates and server packages require separate verification.
Does a working website establish safety?
No. Ordinary requests can succeed on a vulnerable runtime. The absence of visible changes also cannot exclude an earlier intrusion.
Useful evidence combines the package revision, vendor confirmation covering both CVEs and verification of active web processes. Signs of compromise require a separate investigation.
Sources
- PHP: Security advisory, 21 October 2026
- PHP Foundation: security release and background
- PHP: CVE-2026-109618
- PHP: CVE-2026-109619
- ENISA: joint statement
- NHS England: PHP2Shell
- cPanel: corrected package revisions
- Google Cloud: runtime updates and redeployment
- W3Techs: PHP usage, 22 October 2026
Additional primary sources and documentation
- PHP: pinned PHP 7.4 security backport
- PHP: Unix installation
- PHP: FPM compilation options
- APT: apt-get options and simulation
- Debian: CVE-2026-109618
- Debian: CVE-2026-109619
- AWS: MadPot observations before disclosure
- Datadog Security Labs: reproduction, detection and remediation
Further analysis and reporting
The additionally suggested Prophet Security article was unavailable during research and was not used as evidence.