LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Blog Overview /Web Development / PHP2Shell: What Provides Protection and Why Unpatched...
Read this article in other languages:

PHP2Shell: What Provides Protection and Why Unpatched Websites Will Remain

PHP2Shell: What Provides Protection and Why Unpatched Websites Will Remain
Contents
  1. Updated situation: attempts before public disclosure
  2. What PHP2Shell means
  3. Which updates provide protection?
  4. From updated packages to protected services
  5. The consequences of a missing update
  6. Will administrators actually install the updates?
  7. How many websites will remain at risk?
  8. Linux operations: inventory the runtime actually in use
  9. Updating PHP 8.4 with APT
  10. Updating PHP 8.5 with APT
  11. Updated packages do not prove updated running processes
  12. PHP 7.4: a security backport instead of unchanged 7.4.33
  13. Investigation: correlate multiple signals
  14. Recognizing PHP2Shell in logs: suspicion is not proof
  15. Initial Linux log queries
  16. Improve logging without collecting form bodies
  17. Protection while an update is pending
  18. Concrete intrusion indicators: separate patching from incident response
  19. Questions and answers
  20. 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]

Four verification steps: inventory, patch both flaws, replace running processes and verify web operation
The update must reach the running web service.

From updated packages to protected services

  1. Inventory: Domains, PHP pools, containers, forgotten test installations and embedded administration tools all belong in scope.
  2. Evidence: Each runtime needs a recorded package source, revision and confirmation that both CVEs are addressed.
  3. 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]
  4. 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:

ShellLinux: inventory PHP installations
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

ShellPHP 8.4: repository and upgrade plan
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:

ShellPHP 8.4: installation and FPM checks
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 -v

Updating 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.

ShellPHP 8.5: repository and upgrade plan
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.

ShellPHP 8.5: installation and FPM checks
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 -v

Apache 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:

ShellFind old PHP process executables on Linux
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
BASH

A 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.

ShellPHP 7.4: pin the official security commit
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' HEAD

The 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

ShellPHP 7.4: minimal build and tests
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 test

Failed 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:

ShellPHP 7.4: isolated installation and identification
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 HEAD

The 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.

ObservationInterpretationNext check
Many POST requests or large uploadsAlso normal for imports and legitimate uploadsTarget route, time and deviation from the usual workload
Memory exhaustion, worker termination or segfaultA lead, not a unique PHP2Shell signatureRelated requests, pool, PID and known software defects
Unexpected shell or download tool as a child processHigher priority; some applications legitimately launch such programsParent process, actual purpose and timing
Unauthorized code change or proven foreign commandConcrete evidence suggesting compromiseIsolation, 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

ShellJournal: memory errors and worker failures
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:

ShellAccess logs: inspect POST requests
sudo zgrep -hE '"POST [^"]+ HTTP/[0-9.]+"' \
  /var/log/nginx/access.log* /var/log/apache2/access.log* |
tail -n 100

Missing 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:

ShellCurrent processes and parent processes
ps -eo pid,ppid,lstart,user,comm --forest

This 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

nginxnginx: additional metadata format
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.

nginxnginx: enable the format for the selected host
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

  1. PHP: Security advisory, 21 October 2026
  2. PHP Foundation: security release and background
  3. PHP: CVE-2026-109618
  4. PHP: CVE-2026-109619
  5. ENISA: joint statement
  6. NHS England: PHP2Shell
  7. cPanel: corrected package revisions
  8. Google Cloud: runtime updates and redeployment
  9. W3Techs: PHP usage, 22 October 2026

Additional primary sources and documentation

Further analysis and reporting

The additionally suggested Prophet Security article was unavailable during research and was not used as evidence.

Log investigation documentation

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in touch

A question or an idea for a web project? Tell me a little about it. This form uses reCAPTCHA to protect against spam.

Write a comment

Edge cases the article misses and questions about the code are welcome here.

The email address is not published. Required fields are marked with an asterisk.