Skip to content
Exploit for Improperly Controlled Sequential Memory Allocation in Php

Exploit for Improperly Controlled Sequential Memory Allocation in Php

Sploitus • October 21, 2026

![Name](

![CVSS](

![Disclosed](

![Shell](

![License](

![Version](

[![CI](

Read-only audit script for **PHP2Shell**: **CVE-2026-109618** and **CVE-2026-109619**. Both are use-after-free bugs in the PHP engine itself. An unauthenticated attacker can get remote code execution as the web worker user, or read leftover worker memory, before any application code runs.

CVE-2026-109618 + CVE-2026-109619 PHP cross-request use-after-free

A PHP worker serves many requests in a row. Both bugs leave the worker holding pointers into memory that belonged to an earlier request:

| CVE-2026-109618 | Memory exhaustion: PHP `longjmp()`s out while request memory is half initialised | Request shutdown, and the session upload-progress unserializer during multipart parsing |

| CVE-2026-109619 | Fatal errors, uncaught errors, failed `require`, timeouts; saved fatal-error backtraces on 8.5; OPcache state | INI diagnostics before request start, session upload progress |

The known trigger is a `multipart/form-data` POST, which PHP parses before the script starts. No login, no form and no vulnerable application code are needed. Clean WordPress, Drupal, Magento and the rest are all exposed, because the bug is in PHP.

| Affected | PHP 5.4.0 through 8.5.11, every web SAPI: mod_php (highest risk), php-cgi, PHP-FPM, LiteSpeed, FrankenPHP |

| Fixed upstream | **8.5.12, 8.4.27, 8.3.36, 8.2.35** (2026-10-21) |

| End of life | 7.4–8.1: source backports only, no release. 5.4–7.3: no fix |

| Debian | trixie `php8.4 8.4.27-1~deb13u1` (DSA-6574-1), bookworm `php8.2 8.2.35-1~deb12u1` (DLA-4848-1) |

| Ubuntu | USN-8948-1/-2: jammy `php8.1 8.1.2-1ubuntu2.28`, noble `php8.3 8.3.6-0ubuntu0.24.04.13`, resolute `php8.5 8.5.4-0ubuntu1.5` |

| RHEL family | errata per stream; AlmaLinux 8/9/10 shipped on release day |

Distro states are a 2026-10-21 snapshot. Distros backport without bumping the PHP version, so on a distro build **`php -v` proves nothing**: the package changelog decides.

It only triages and changes nothing. It never sends a request, never runs a PoC and never edits `php.ini` or pool configs. It does execute the PHP binaries it finds, with `-v` and a read-only `ini_get()` snippet.

- **Finds every PHP build** in the usual system prefixes: distro and Sury (`/usr/bin/php*`, `/usr/sbin/php-fpm*`), Remi, cPanel EA, CloudLinux alt-php, Plesk, LiteSpeed `lsphp`, FrankenPHP, and the `php-cgiNN` / `php.binNN` zoo of shared hosting. Symlinks are counted once; `php-config`/`phpize` are skipped. `--php PATH` adds more.

1. a changelog that names CVE-2026-109618/109619 or php2shell always wins (`changelog.Debian.gz`, also via `php8.x-common`; `rpm -q --changelog`)

3. otherwise the upstream thresholds; a distro build with no readable changelog stays UNKNOWN, because a backport is possible

- **Works out what serves the web**: the SAPI (`cgi-fcgi`, `fpm-fcgi`, `litespeed`, `frankenphp`) and Apache `LoadModule php_module` lines. A `libphp8.4.so` ties mod_php to the 8.4 CLI; an unversioned `libphp.so` makes every CLI build count.

- **Reports the interim settings** per build and per FPM pool: `memory_limit=-1`, `session.upload_progress.enabled=0`, `fatal_error_backtraces=0` on 8.5, `pm.max_requests=1`.

- **Scans web roots** (`/var/www`, `/srv/www`, …; without root also `~/public_html`, `~/www`, `~/WWW`, `~/domains`) for per-directory ini overrides, PHP changed since `--since`, PHP in `uploads/`, and web-shell patterns.

- **Hunts** the journal and Apache/FPM/LiteSpeed error logs for worker crashes (`segfault`, `exited on signal 11`, `AH00052`), and the live process table for a shell or downloader whose parent is a PHP worker or Apache.

- Produces a risk score from 0 to 100 and a verdict.

It runs fine **without root**, which is the normal case on shared hosting. It then sees only what that user can read.

- **PATCHED**: every affected build carries the fix.

- **VULNERABLE**: at least one unfixed build serves web requests.

- **MITIGATED**: unfixed builds exist, but none of them serves the web (for example a leftover CLI 8.1 to a patched FPM 8.4). The bug needs a web request, but pointing a server at such a build reopens it at once.

- **UNKNOWN**: a build could not be classified: a distro package below the upstream version with no readable changelog, or a branch not in the table.

- **NOT_APPLICABLE**: no PHP between 5.4.0 and the fixed releases.

The interim settings and per-directory overrides **never change the verdict**. PHP says they reduce exposure but do not make a host safe, and that `.user.ini` and `ini_set()` apply too late. They only lower the score.

./php2shell-audit.sh # full text report

./php2shell-audit.sh --json # machine-readable JSON, no banner

./php2shell-audit.sh --quiet --no-hunt # one-line verdict for mass scans

./php2shell-audit.sh --php /opt/custom/bin/php # audit an extra build (repeatable)

./php2shell-audit.sh --webroot /srv/sites # scan this web root (repeatable)

./php2shell-audit.sh --no-webroot # skip the web root scan

./php2shell-audit.sh --since 2026-10-15 --until 2026-10-22 # IoC window

On a shared host without a copy of the script:

ssh user@host 'bash -s -- --no-banner' < php2shell-audit.sh

As root, the script refuses to execute a PHP binary that is not root-owned or is group/world writable.

| 1 | `MITIGATED` (unfixed builds, none serves the web) |

| 2 | `VULNERABLE` (an unfixed build serves web requests) |

| 3 | `UNKNOWN` (treat as suspect on anything public-facing) |

| 4 | `ERROR` (script could not run; missing tools or bad environment) |

These are the same as in `cve-2026-99999-audit`, `cve-2026-46331-audit` and `linux-pagecache-cve-audit`.

See [`examples/sample-output.txt`](examples/sample-output.txt) for an unpatched Debian 13 host with mod_php 8.4, distro FPM and a leftover CLI 8.1, and [`examples/sample-output.json`](examples/sample-output.json) for the JSON form. Regenerate them with `bash tests/make_examples.sh`.

| Verdict `VULNERABLE` or `MITIGATED` | +50 |

| An unfixed or unknown build serves the web | +15 |

| …and the interim settings are missing (neither global ini nor every FPM pool) | +10 |

| IoCs (worker crashes, spawned shells, web-shell patterns) | +10 |

The score is capped at 100. An unfixed CLI-only host scores 50, an unfixed php-cgi/FPM host 75, unfixed mod_php 85.

ansible -i inventory all -m script -a "php2shell-audit.sh --json --no-hunt" --become | tee /tmp/audit.jsonl

grep -v '^[a-z]' /tmp/audit.jsonl | jq -s 'group_by(.verdict) | map({verdict: .[0].verdict, hosts: length})'

ansible.builtin.script: php2shell-audit.sh --quiet --no-hunt

## Mitigations (if you need to delay patching)

Update and **restart every worker** (`systemctl restart php8.4-fpm apache2`); a running worker keeps the old code. On shared hosting, ask the provider and move the site to the newest branch they offer.

Until then, in `php.ini`, the FPM pool or the server config (not `.user.ini`):

memory_limit = -1 ; counter-intuitive; cap memory with cgroups instead

fatal_error_backtraces = 0 ; PHP 8.5 only

php_admin_flag[session.upload_progress.enabled] = off

pm.max_requests = 1 ; breaks the same-worker reuse

- Avoid `RLIMIT_AS` / `RLIMIT_DATA`; PHP says they make the attack easier.

- If the site needs no uploads, block `multipart/form-data` at the web server or WAF. That breaks uploads and some checkout forms.

- Hiding errors (`display_errors`, `error_reporting`, `@`) does nothing.

- End-of-life PHP behind a VPN or off the public network.

- The web-SAPI ini is read from the global config that CLI/CGI see. Per-vhost `php_admin_value`, Plesk/cPanel per-domain settings and `PHP_VALUE` passed by a frontend are not visible; FPM binaries get no ini probe (their pools are read instead).

- Log files are grepped whole, not by date; only the journal honours `--since`/`--until`.

- A clean exploit leaves no crash, so zero IoCs proves nothing.

- Custom builds (shared hosting) are judged by version only; a provider could in theory patch without bumping it.

bash tests/run_tests.sh # 55 cases: fake PHP builds + mocked dpkg/rpm/ps/journalctl; must not run as root

The test hooks (`PHP2SHELL_TEST_PATH`, `PHP2SHELL_TEST_ROOT`) are ignored when EUID is 0. Every JSON case also asserts the mocked hostname, which catches mocks silently bypassed by the pinned `PATH`. The web-shell fixture is assembled from pieces so antivirus scanners don't lock the test file.

- PHP: (8.5.12 / 8.4.27 / 8.3.36 / 8.2.35)

- Blacklens analysis:

- Sansec:

- JFrog:

- Tenable FAQ:

- Ubuntu:

- AlmaLinux:

For authorised defensive use on systems you own or are explicitly permitted to audit. The script never runs a PoC and never modifies system state. Treat the verdict as triage, not as proof of compromise or proof of safety.