Skip to content
Attack Surface Management

php2shell (CVE-2026-109618 and CVE-2026-109619): PHP Core Pre-Auth RCE Detection with Sn1per

xer0dayz · · 23 min read

CVE-2026-109618 and CVE-2026-109619 are two critical remote code execution vulnerabilities in PHP itself, not in a framework, a plugin or a theme. The PHP security team rates both CVSS v4.0 9.2, both are reachable without authentication, and every PHP branch still in support was affected until the fixed releases landed on 21 October 2026 at 14:00 UTC. Researchers nicknamed the pair php2shell. The fixes are PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12.

The scope is unusually wide even by the standards of a critical CVE. This is the language runtime, so it does not matter whether the application on top is WordPress, Magento, Laravel, Drupal, Shopware or twenty thousand lines of bespoke code nobody has opened since 2019. It also cannot be fixed in application code. The remediation is a runtime update followed by a worker restart, which means the people who need to act are frequently not the people who own the application.

This post is three things. First, a plain-English account of what these two vulnerabilities actually are, taken from the PHP advisories rather than from the pre-patch reporting, because the two do not entirely agree. Second, a Nuclei template you can run right now that identifies unpatched PHP across your estate without sending anything resembling an exploit. Third, the part that takes longer than patching: working out which hosts on your perimeter are running PHP at all, including the ones that are not in your asset inventory. Credit for the reports goes to the researchers named in the PHP advisories, lcfr-hax, sagitzadik-wiz, tomash-areion and amyb-oai, and the php2shell name was coined by Calif.IO.

php2shell at a glance

  • CVE-2026-109618, “RCE via OOM corruption” – PHP advisory GHSA-w8hw-w9fx-j79w. CVSS v4.0 9.2 Critical, vector CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H. CWE-416, CWE-755, CWE-824, CWE-1325 and CWE-1419.
  • CVE-2026-109619, “RCE via stale global state” – PHP advisory GHSA-6wmf-4rv5-p3rp. Identical CVSS score, vector and CWE set.
  • Affected – PHP below 8.2.35, below 8.3.36, below 8.4.27 and below 8.5.12.
  • Fixed – 8.2.35, 8.3.36, 8.4.27 and 8.5.12, released 21 October 2026.
  • No fix at all – PHP 8.1 and earlier are end of life and receive no upstream patch. PHP 8.2 is itself out of security support on 31 December 2026, so 8.2.35 buys you about ten weeks.
  • Server APIs in scope – PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed. php.net states the risk is highest for mod_php, where a demonstrated exploit lets an unauthenticated request trigger the flaw before any application code runs.
  • Privileges obtained – code execution as the PHP worker user, which on a typical LAMP host means read access to database credentials, application secrets and every file the web user can reach.
  • Exploitation status – neither CVE is in the CISA Known Exploited Vulnerabilities catalog at the time of writing, and no indicators of compromise have been published. Reporting on in-the-wild activity conflicts.
  • Not in NVD yet – neither identifier had an NVD record when this was written. The authoritative sources are php.net’s release announcement and the two GitHub Security Advisories.

What is CVE-2026-109618?

CVE-2026-109618 is a memory corruption flaw in how PHP handles running out of memory.

PHP uses longjmp() to bail out of a context when an allocation fails. That is a blunt instrument: it jumps the program counter somewhere else without unwinding the stack or giving the abandoned code a chance to tidy up after itself. Most of the time this is fine, because the places PHP jumps from are places where nothing is half-built.

The bug is that in some contexts, request memory is briefly reachable before it has been fully initialized. If an allocation fails inside that window, the jump leaves the request in a state the engine believes is valid and is not. Request cleanup then runs over that partially-initialized data and treats invalid values as real ones.

Two properties make this interesting from a defender’s point of view. The first is that an attacker who can influence memory pressure can influence when allocations fail, and request size is one of the easiest things in the world for a remote client to control. The second is that the damage happens during request shutdown, which runs after your application has finished and in many cases after the response has already gone out. Nothing in your application’s logs will look wrong.

The official vector records attack complexity as high and attack requirements as present, the AC:H/AT:P part of the string. That is worth knowing, because it means this is not a single request you can spray at the internet and expect to land. It needs the right conditions. It is not a reason to deprioritize: the consequence is pre-auth code execution on the web tier, and the complexity cost falls on the attacker only once, when they write the tooling.

What is CVE-2026-109619?

CVE-2026-109619 is a use-after-free reached through PHP’s own global state, and the mechanism is worth understanding because it explains why one of the mitigations being passed around is incomplete.

PHP workers are reused. A PHP-FPM or mod_php worker process handles a request, resets itself, and handles the next one, potentially thousands of times. Some engine features need to keep a pointer to request-scoped memory in a global, and the contract is that whatever sets that global also resets it when the request ends.

The flaw is that a fatal error can break the contract. A fatal error, including one caused by memory exhaustion, can abandon an operation before it reaches the reset that was supposed to pair with it. The global survives. The memory it points at does not. The next request handled by that same worker then reads through a pointer into freed memory, before normal initialization gets around to clearing it.

That cross-request shape is what makes it dangerous. The attacker’s first request only has to cause a fatal error at the right moment. The second request, which may be anyone’s, is where the stale pointer gets read. The PHP advisory names two confirmed paths into this: fatal error backtraces on PHP 8.5, and the session upload-progress lifecycle on multiple versions.

That second path deserves a direct word, because a great deal of the advice published between the leak and the patch presented it as the root cause of both CVEs and session.upload_progress.enabled=0 as the fix. Per the PHP advisories, upload progress is one route into CVE-2026-109619 and nothing to do with CVE-2026-109618. Disabling it is worth doing and it is not sufficient. The advisory for CVE-2026-109619 is explicit: updating to a release containing the complete state reset patch is the only reliable mitigation, and no complete configuration workaround exists.

Affected and fixed versions

PHP branch Affected Fixed release Notes
8.5.x before 8.5.12 8.5.12 Also the only branch exposed via the fatal error backtrace path
8.4.x before 8.4.27 8.4.27
8.3.x before 8.3.36 8.3.36
8.2.x before 8.2.35 8.2.35 Security support ends 31 December 2026
8.1.x and earlier all none End of life, no upstream fix, upgrade the branch

Four separate floors, which is where version checks usually go wrong. A naive “anything below 8.5.12 is vulnerable” rule flags a perfectly patched 8.3.36 host, and the reverse comparison clears a vulnerable 8.4.26 one. Any tooling you point at this needs testing against each branch boundary rather than trusting, which is why the template below ships with a boundary test harness.

Two traps beyond the version arithmetic.

A patched package is not a patched worker. PHP-FPM and Apache load the engine once and keep it resident. If the package was updated but the service was not restarted, the vulnerable code is still executing in every live worker while php -v on the command line cheerfully reports the fixed version. This is the single most common way an organization believes it has remediated and has not.

Distribution version strings do not move. Debian, Ubuntu and RHEL backport security fixes without changing the upstream version number, so a fully patched Debian host can keep advertising 8.3.35 forever. If your estate is distribution-packaged, the upstream numbers in the table above are the wrong thing to compare against and you want the matching DSA, DLA, USN or RHSA instead. This cuts both ways for detection and it is the reason the section below spends as much time on false positives as on true ones.

How the exploit chain works

The shape of the attack, with the payload position redacted:

POST / HTTP/1.1
Host: www.example.com
Content-Type: [REDACTED: a body type and structure chosen so that PHP
               allocates while parsing it, before application dispatch]
Content-Length: [REDACTED]

[REDACTED: a request body sized and shaped to drive the engine into an
 allocation failure at a specific point in request setup]

The mechanism in prose. Both bugs need the engine to hit an error at a precise moment in the request lifecycle rather than at a moment of the application’s choosing. For CVE-2026-109618 that moment is while request memory is reachable but not yet fully initialized, and the lever is memory pressure during early request parsing. For CVE-2026-109619 the attacker instead wants a fatal error that skips a reset, leaving a global pointing at memory that is about to be freed, and then a second request on the same worker to read through it. In both cases the interesting work happens in the engine before or after the application runs, which is precisely why no amount of application-level validation helps.

We are deliberately not publishing a working exploit here. Public proof-of-concept code already exists for this pair and reproducing it adds nothing a defender can use while shortening the distance for everyone else. The same editorial line applies to every advisory in this series, including our XSS2Shell advisory and the earlier wp2shell write-up, where the detection we shipped was a passive version check rather than the active exploit circulating at the time.

One more consequence of where these bugs live, and it is the most operationally important sentence in this post. PHP parses the request before your application code runs, so a WordPress plugin, a Laravel middleware or an application-layer rule never sees the request at all. Mitigation has to sit either in the runtime configuration or in front of the web server. There is no in-application fix, and there is no plugin that will save you.

Am I affected? Quick manual checks

The authoritative check is local. On each host:

php -v
php-fpm -v 2>/dev/null || php-fpm8.3 -v 2>/dev/null

Then, and this is the step that gets skipped, confirm what the running workers are executing rather than what is on disk:

systemctl status php8.3-fpm apache2 --no-pager | grep -E 'Active|since'
ps -eo lstart,cmd | grep -E '[p]hp-fpm|[a]pache2' | head

If the process start time predates the package update, the worker is still running the vulnerable engine. Restart it.

If you are on a distribution package, compare against your vendor rather than against upstream:

dpkg -l 'php*' | grep -E '^ii'     # Debian, Ubuntu
rpm -q php php-fpm                 # RHEL, Rocky, Alma

Check the interim settings while you are there, remembering that session.upload_progress.enabled cannot be changed with ini_set() and has to come from a configuration file:

php -i | grep -E 'session.upload_progress.enabled|fatal_error_backtraces|memory_limit|post_max_size'

The remote fingerprint is simpler and much less reliable. PHP advertises its version in the X-Powered-By response header whenever expose_php is on, which is the default:

curl -sI https://www.example.com | grep -i 'x-powered-by'

What that signal can and cannot tell you: a version is good evidence of the engine on disk and no evidence at all about whether the workers were restarted, whether a distribution backport has already fixed it, or which server API is in use. And an absent header means “unknown, go and look”, never “patched”. Those limits are real enough that they get their own subsection below rather than a footnote.

Nuclei detection template for CVE-2026-109618 and CVE-2026-109619

There is no upstream Nuclei template for either CVE at the time of writing. The template request on the nuclei-templates repository is open with nothing merged against it, and nuclei -ut pulls tagged releases rather than the development branch, so “my templates are up to date” will not mean this pair is covered for some weeks yet. Here is one you can use now.

It is passive. It issues one plain GET for the document root and reads a response header. It sends no multipart body, no oversized request and no payload of any kind, and it never attempts to trigger either vulnerability.

id: CVE-2026-109618

info:
  name: PHP Core - Pre-Auth RCE Patch Gap (CVE-2026-109618, CVE-2026-109619) [php2shell]
  author: xer0dayz
  severity: critical
  description: |
    PHP releases below 8.2.35, 8.3.36, 8.4.27 and 8.5.12 are affected by two
    critical remote code execution vulnerabilities disclosed on 2026-10-21 and
    nicknamed php2shell. Both are rated CVSS v4.0 9.2 by the PHP security team
    and both are reachable by an unauthenticated request.

    This template is NON-DESTRUCTIVE. It issues a single plain GET for the
    document root and reads the PHP version from the X-Powered-By response
    header. It sends no multipart body, no oversized request, no payload of any
    kind, and it does not attempt to trigger either vulnerability.
  remediation: |
    Update to PHP 8.2.35, 8.3.36, 8.4.27 or 8.5.12 as appropriate for the
    branch in use, then RESTART the PHP workers. A package update that has not
    restarted PHP-FPM or Apache leaves the vulnerable code resident in running
    worker processes, so the host stays exploitable while reporting a patched
    version on disk.
  reference:
    - https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w
    - https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp
    - https://www.php.net/archive/2026.php
  classification:
    cvss-metrics: CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H
    cvss-score: 9.2
    cve-id: CVE-2026-109618,CVE-2026-109619
    cwe-id: CWE-416,CWE-755,CWE-824,CWE-1325,CWE-1419
  metadata:
    verified: false
    max-request: 1
    vendor: php
    product: php
    shodan-query: "X-Powered-By: PHP/"
  tags: cve,cve2026,php,rce,php2shell,unauth,patch-gap

http:
  - method: GET
    path:
      - "{{BaseURL}}"

    redirects: true
    max-redirects: 2

    matchers-condition: or
    matchers:
      - type: dsl
        name: php-82-below-8.2.35
        dsl:
          - "compare_versions(trim_prefix(xpb, 'PHP/'), '>= 8.2.0', '< 8.2.35')"

      - type: dsl
        name: php-83-below-8.3.36
        dsl:
          - "compare_versions(trim_prefix(xpb, 'PHP/'), '>= 8.3.0', '< 8.3.36')"

      - type: dsl
        name: php-84-below-8.4.27
        dsl:
          - "compare_versions(trim_prefix(xpb, 'PHP/'), '>= 8.4.0', '< 8.4.27')"

      - type: dsl
        name: php-85-below-8.5.12
        dsl:
          - "compare_versions(trim_prefix(xpb, 'PHP/'), '>= 8.5.0', '< 8.5.12')"

      # End-of-life branches receive no upstream fix at any patch level, so the
      # version alone is decisive and remediation is an upgrade, not a patch.
      - type: dsl
        name: php-eol-no-vendor-fix
        dsl:
          - "compare_versions(trim_prefix(xpb, 'PHP/'), '< 8.2.0')"

    extractors:
      # Single-value kval against one header. Deliberately NOT a multi-alternate
      # regex across X-Powered-By and Server: when both headers carry a PHP
      # version and they disagree, a multi-match regex extractor binds a list
      # and compare_versions silently returns false, which is a false negative.
      # Behind a reverse proxy that is a likely case, not a theoretical one.
      - type: kval
        name: xpb
        part: header
        internal: true
        kval:
          - x_powered_by

      - type: kval
        name: php_version_banner
        part: header
        kval:
          - x_powered_by

Run it:

nuclei -t CVE-2026-109618.yaml -validate
nuclei -t CVE-2026-109618.yaml -u https://www.example.com
nuclei -t CVE-2026-109618.yaml -l php-hosts.txt -jsonl -o php2shell.jsonl

A match looks like this, with the branch named explicitly so triage does not have to re-derive it:

[CVE-2026-109618] [http] [critical] https://www.example.com [php-83-below-8.3.36] ["PHP/8.3.35"]

Why the extractor is a kval and not a regex

This is a small detail with a sharp edge. The obvious way to pull a PHP version out of a response is a regex extractor over the header block with two alternates, one for X-Powered-By and one for Server. The upstream PHP end-of-life template does exactly that.

The problem is that a regex extractor returns every match, and when it returns more than one, the DSL variable binds a list instead of a string. compare_versions against a list returns false. Not an error, not a warning: false. So on any host where both headers carry a PHP version and the two disagree, which is the normal situation behind a reverse proxy or a caching layer, a two-alternate regex produces a silent false negative on exactly the hosts you most want to find.

A kval extractor reads one named header and yields one value, so the ambiguity never arises. The cost is that a host which advertises PHP only in its Server header is not matched by the template. The bundled Python checker covers that case instead, where precedence between the two headers can be made explicit.

Three blind spots, stated plainly

expose_php=Off makes this template blind. No header, no version, no match. A host that returns nothing is unknown and needs a local check. The uncomfortable part is that setting expose_php=Off is standard hardening advice, so this template is least effective against the best-maintained hosts in your estate and most effective against neglected ones. That bias happens to be useful, but do not mistake silence for safety.

Distribution backports produce false positives. A patched Debian or RHEL host still advertises its old upstream version, so it will match. Any hit against distribution-packaged PHP has to be confirmed with dpkg -l or rpm -q against the vendor advisory before anyone writes it up. On a large distro-packaged estate this check will generate more false positives than true ones, and running it without that confirmation step will burn your credibility with the team that has to action the output.

The template cannot see the server API. php.net singles out mod_php as the highest-risk configuration, and a version string says nothing about whether the host runs mod_php, PHP-FPM, php-cgi, FrankenPHP or LiteSpeed. Nor can it tell you whether the workers were restarted after patching. Both of those require local access, and both can turn an apparently-clean host into a vulnerable one.

Detecting php2shell across your attack surface with Sn1per

The template is the easy half. For a runtime vulnerability the hard half is the host list, because PHP is not something an organization deploys deliberately so much as something that accumulates. The marketing microsite from a campaign three years ago. The staging copy on a subdomain nobody decommissioned. The vendor appliance with a PHP admin interface. The WordPress install behind a hostname that is not in the inventory because it was provisioned on someone’s corporate card.

Those are the hosts that stay unpatched, and none of them are in your CMDB. So treat it as a discovery problem before a scanning problem.

# Discover first: subdomains, certificate transparency, ports, services
sniper -t example.com -m recon -w php2shell

# Then the web pass, which fires the template on every HTTP and HTTPS port found
sniper -t www.example.com -m web -w php2shell

# Sweep a known fleet
sniper -f php-hosts.txt -m webscan -w php2shell

Drop the template into /sniper/templates/nuclei/server/ and it is picked up with no configuration change, because NUCLEI_TEMPLATES is a directory list that Nuclei recurses. Findings land in the workspace as critical, carrying the matcher name and the version banner, so the branch and the fix floor are both visible without opening the template.

Worth knowing if you are wiring this into an existing workflow: Sn1per has been collecting the data this template needs all along. The raw header capture in a web scan already records X-Powered-By, and webtech and whatweb both report a PHP version. What was missing was anything that compared that version against a fix floor and raised a finding. That is the entire job of the template, and it is why this took an afternoon rather than a sprint.

Pull the results out of a Professional or Enterprise install over the API:

curl -sk -H "X-API-Key: $SN1PER_API_KEY" \
  "https://sn1per.local/pro/api.php?action=vulnerabilities&workspace=php2shell" \
  | jq -r '.[] | select(.name | test("109618")) | [.host, .severity, .details] | @tsv'

One note on where this scan runs. Its output is a ranked list of your internet-facing hosts running an unpatched language runtime with public proof-of-concept code available. That is a sensitive document with a short shelf life, and it is not an obvious thing to generate inside a multi-tenant SaaS scanner. Sn1per runs on your own hardware, on your own network, and the findings stay there. The general form of that argument is in our write-up on external attack surface management with Sn1per.

Which Sn1per edition fits

Capability Community Professional 2026 Enterprise
Run the php2shell template Yes Yes Yes
Curated Nuclei set maintained for you Partial Yes Yes
Workspaces and scan history Basic Yes Yes
Web UI with asset risk ranking No Yes Yes
JSON API for finding export No Yes Yes
Scheduled recurring surface sweeps No Limited Yes
Multi-user, delegated workspaces No No Yes

For a one-off answer to “is anything on our perimeter running unpatched PHP”, Community plus the template is enough, and that is a reasonable afternoon’s work. The recurring version of the question is the one that matters more here, because PHP ships security releases on a monthly cadence and this will not be the last one. Scheduling and scan history are what turn a single answer into a trend line. There is more on that trade-off in our write-up of automated penetration testing tools.

Remediation and mitigation

The first two items are the official guidance. Everything after them is defence in depth, and the distinction matters.

  1. Update PHP, then restart the workers. 8.2.35, 8.3.36, 8.4.27 or 8.5.12 depending on your branch. Then systemctl restart php8.3-fpm or systemctl restart apache2 for the relevant units. The restart is not optional and it is the step that gets missed. On a distribution package, take your vendor’s update rather than upstream’s, and track the DSA, DLA, USN or RHSA rather than the upstream version number.
  2. Upgrade end-of-life branches rather than trying to patch them. PHP 8.1 and earlier get no fix. A CVSS 9.2 with no vendor fix path on an internet-facing host is an architecture problem, not a patching one, and it belongs above the merely-unpatched hosts in your queue. Note that 8.2 is out of security support on 31 December 2026, so if you are moving anyway, move past it.
  3. Apply the documented interim settings only if you genuinely cannot patch today. The PHP advisory for CVE-2026-109619 states plainly that updating is the only reliable mitigation and no complete configuration workaround exists. With that caveat: set fatal_error_backtraces=0 on 8.5, set session.upload_progress.enabled=0 where upload progress is unused (a configuration file only, ini_set() cannot touch it), and reduce fatal errors on attacker-reachable paths. For CVE-2026-109618 the advisory suggests memory_limit=-1 set administratively, a conservative post_max_size, a reduced max_input_nesting_level, and limiting multipart parts and headers at the frontend.
  4. Consider pm.max_requests=1 as a short-term stopgap. Recycling the PHP-FPM worker after every request removes the cross-request window that CVE-2026-109619 depends on. It costs real performance, so this is a measure for a weekend, not a quarter.
  5. Confine the worker with AppArmor or SELinux. This is the one control on the list that blunts the outcome rather than the trigger. A PHP worker that cannot exec is a much less useful foothold, whatever memory corruption an attacker achieves inside it, and the profile keeps paying off against the next runtime CVE too.
  6. Resist the urge to block multipart/form-data wholesale. It circulated widely as an emergency measure. It breaks every upload and a good number of login forms, and it does not cover the fatal-error path into CVE-2026-109619, so you take the outage and keep part of the exposure. If you need a frontend control, limit part counts and header counts as the advisory suggests rather than refusing the content type.
  7. Rotate what the web tier could reach. Code execution as the worker user means database credentials, API keys and application secrets in configuration files were all readable. If you have reason to think a host was exploited, patching does not revoke a credential.

If you were unpatched: hunting for exploitation

No indicators of compromise have been published for either CVE, so there is no feed to subscribe to. What follows is detection logic rather than signatures, and each signal has benign causes that you have to rule out.

The failed-attempt signal is a worker crash. Memory corruption usually crashes before it works, so a rise in PHP-FPM segmentation faults correlated with POST volume is worth alerting on:

grep -iE 'signal 11|SIGSEGV' /var/log/php8.3-fpm.log
journalctl -u php8.3-fpm --since '24 hours ago' | grep -iE 'signal|core dump'

The canonical shape is a child exiting on signal 11 and being respawned immediately afterwards. Two caveats, both important. Benign causes are common: the JIT, opcache.file_cache, kernel out-of-memory kills, and unrelated crash bugs all produce the same line, so check dmesg | grep -i oom before concluding anything. And more significantly, this only catches failures. A successful memory-corruption attack typically returns a normal 200 and no crash at all, so an absence of segfaults is not an absence of exploitation.

The reliable signal is process lineage. A PHP worker has almost no legitimate reason to parent a shell or a downloader. If you have endpoint telemetry on your web tier, alert on these pairs:

php-fpm  -> sh, bash, dash
apache2  -> bash, python, perl
httpd    -> curl, wget, nc

This is the detection that works regardless of which CVE, which payload, or which year, and it is worth building even if you patched this morning.

Host artifacts to sweep for. New single-file PHP in a web root, particularly with obfuscated content; changes to .user.ini or the auto_prepend_file setting, which is a quiet way to get code running on every request; new cron, systemd or Supervisor entries; unexpected SSH keys; and outbound connections from a web host to a destination it has never contacted before.

find /var/www -type f -name '*.php' -newermt '2026-10-20 15:00' 2>/dev/null
find /var/www -name '.user.ini' -o -name '.htaccess' -newermt '2026-10-20 15:00' 2>/dev/null

Expect noise from caches and generated files. The point is to narrow, not to conclude.

If you do find something, treat it as a break-in rather than a finding. Preserve the web server access and error logs, the PHP logs and any endpoint telemetry before you rebuild, because the orchestrator may well have already recycled the compromised worker out from under you.

Frequently asked questions

What is CVE-2026-109618?
It is a critical remote code execution vulnerability in PHP core, titled “RCE via OOM corruption” in the PHP advisory GHSA-w8hw-w9fx-j79w and rated CVSS v4.0 9.2. PHP uses longjmp() to exit a context when an allocation fails, but in some contexts request memory is reachable before it is fully initialized, so an out-of-memory condition in that window leaves the request corrupted and request cleanup then treats invalid data as valid. It is reachable by an unauthenticated request, and php.net reports the risk is highest under Apache mod_php.

Which PHP versions are affected by CVE-2026-109618 and CVE-2026-109619?
Everything below 8.2.35, 8.3.36, 8.4.27 and 8.5.12. The fixes are those four releases, published 21 October 2026. PHP 8.1 and earlier are end of life and get no upstream fix, so those hosts need a branch upgrade. Be careful with distribution packages: Debian, Ubuntu and RHEL backport fixes without changing the version string, so compare against your vendor’s advisory rather than the upstream number.

Are CVE-2026-109618 and CVE-2026-109619 being exploited in the wild?
Neither CVE is in the CISA Known Exploited Vulnerabilities catalog at the time of writing, and no indicators of compromise have been published. Public reporting on in-the-wild activity conflicts, with some vendors reporting early attack attempts and others reporting none across their own telemetry. Public proof-of-concept code does exist. Given pre-auth code execution on the web tier, the sensible posture is to patch on the assumption that exploitation is either happening or imminent rather than waiting for confirmation.

Is disabling session.upload_progress enough to fix this?
No, and this is the most common mistake being made with this pair. Upload progress is one of two confirmed exploitation paths into CVE-2026-109619, alongside fatal error backtraces on PHP 8.5, and it has nothing to do with CVE-2026-109618 at all. The PHP advisory states directly that updating is the only reliable mitigation and that no complete configuration workaround exists. Disable it if you do not need it, and still patch. One more detail: the setting cannot be changed with ini_set(), so it has to come from php.ini, .user.ini or .htaccess.

Is the Nuclei template safe to run against production?
Yes. It sends a single plain GET for the document root and reads one response header. There is no multipart body, no oversized request, no payload and no attempt to trigger either vulnerability, so it cannot crash a worker the way a real exploitation attempt can. That matters more than usual here, because the failure mode of an active check against a memory-corruption bug is a segfaulting production web server. Treat any match as a lead rather than a finding until you have confirmed it locally, especially on distribution-packaged hosts.

Closing

The thing worth taking from php2shell is not the specific pair of bugs. It is that the vulnerability was in the runtime, below everything an application team controls, in a component nobody lists as a dependency because it is assumed rather than chosen. No plugin update fixed it, no framework release fixed it, and no application-layer rule could even see the request that triggered it.

That shape is becoming more common, not less, and it rewards a boring capability over a clever one. Knowing exactly which runtime version every internet-facing host is running, and being able to answer that question again next month without redoing the work, outperforms any amount of signature tuning on precisely this class of bug. PHP ships security releases monthly. The inventory you build today answers the next one too.

Grab the template, run it across every HTTP and HTTPS port you own rather than just the hosts you remember, confirm the hits locally before you file them, and check that somebody actually restarted the workers.

Try it free

See your attack surface like a pentester would.

Sn1per finds, ranks, and exploits real vulnerabilities autonomously - the same way attackers do.