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

Exploit for Improperly Controlled Sequential Memory Allocation in Php

Sploitus • October 22, 2026

Detects exposure to the PHP core RCEs **CVE-2026-109618** and **CVE-2026-109619**

("PHP2Shell", published 2026-10-21) in three modes:

| `url` | blackbox scan of a web target over HTTP, no shell access needed |

| `local` | host checks on the machine the scanner runs on |

| `ssh` | the same host checks on a remote host over SSH |

The scanner is a detector, not an exploit. The only request it sends is a benign

`session.upload_progress` arming probe that contains no PHP code unless you ask

Download the binary for your platform from the

[releases page](

| linux x86_64 | `php2shell-scanner-linux-amd64.tar.gz` |

| linux arm64 | `php2shell-scanner-linux-arm64.tar.gz` |

| linux armv6 | `php2shell-scanner-linux-arm.tar.gz` |

| macOS intel | `php2shell-scanner-darwin-amd64.tar.gz` |

| macOS apple silicon | `php2shell-scanner-darwin-arm64.tar.gz` |

| windows x86_64 | `php2shell-scanner-windows-amd64.exe` |

curl -fsSLO

curl -fsSLO

sha256sum --check --ignore-missing SHA256SUMS

tar -xzf php2shell-scanner-linux-amd64.tar.gz

sudo install php2shell-scanner /usr/local/bin/

The binaries are statically linked, so there is nothing to install besides the

php2shell-scanner url -u

php2shell-scanner url -u --json --fail-on critical

php2shell-scanner local --webroot /var/www/html

php2shell-scanner ssh -H 10.0.0.5 -U deploy -i ~/.ssh/id_ed25519

php2shell-scanner ssh -H 10.0.0.5 -U deploy --agent --webroot /srv/www

`url` and `ssh` take repeatable targets. They are scanned in parallel

(`--concurrency`, default 5) and the results come back per target.

php2shell-scanner url -u -u -u

php2shell-scanner ssh -H web1 -H web2 -H deploy@web3:2222

A host can be `host`, `host:port` or `user@host:port`. The port from `-H` wins

over `-p`. `local` stays single target, it is the machine the scanner runs on.

A target that cannot be reached does not stop the run: it gets a

`SCAN_FAILED` or `SSH_CONNECT_FAILED` finding and the other targets are still

php2shell-scanner ssh -H web1 -U deploy --password 'secret'

SSH_PASSWORD='secret' php2shell-scanner ssh -H web1 -U deploy

The same credentials are used for every host in the run. Keys, the agent

(`--agent`) and the password can be combined; the server decides what it

| `--json` | all | print the report as JSON |

| `--fail-on ` | all | exit 1 at this severity or worse (default `high`, `none` disables) |

| `--webroot ` | local, ssh | extra document root to scan; the document roots of the web server configuration are detected automatically |

| `--probe` / `--canary` | url | arming probe on by default; `--canary` adds an inert PHP so Suricata/YARA rules fire |

| `--crawl` | url | look for exposed phpinfo pages (on by default) |

| `--insecure` | url | skip TLS verification |

| `--path` | url | extra endpoint to probe, repeatable |

| `--agent`, `--password`, `--passphrase`, `--known-hosts`, `--insecure-host-key` | ssh | authentication and host key handling |

Exit codes: `0` clean, `1` findings at or above `--fail-on`, `2` usage or runtime error.

| ≤ 8.1 | EOL, no routine updates (7.4/8.0/8.1 backports exist) | treated as affected |

A build below the fix of its branch is reported as **critical**; 8.1 and older are

reported as **critical** end-of-life branches that need a vendor package or the

* PHP version from `X-Powered-By`, the `Server` header, the body, or an exposed

phpinfo page (found with the `?=PHPB8B5F2A0-...` easter egg).

* SAPI: `Apache 2.0 Handler` means mod_php, which is the worst case for

CVE-2026-109618 because request cleanup runs even when startup failed, so the

exploit lands before any application code and application-level authentication

* phpinfo values that decide exploitability: `session.upload_progress.enabled`,

`memory_limit`, `fatal_error_backtraces` (8.5), `session.use_only_cookies`,

* Arming probe: one multipart POST with a `PHPSESSID` field and a

`PHP_SESSION_UPLOAD_PROGRESS` field and no cookie. That is the request shape

the Suricata rules in [php2shell-lab](

key on, so the probe doubles as a correlation point. The probe never contains

a PHP open tag unless `--canary` is given.

The OOM corruption path is **not** probed: proving it means exhausting the

target's memory, which is a denial of service. The scanner reports the

configuration that makes the path reachable instead.

* PHP interpreters and versions (`php`, `php8.x`, `php-fpm`), plus the SAPI in

use: mod_php module present, php-fpm processes, web server processes.

* `session.upload_progress.enabled`, `memory_limit`, `fatal_error_backtraces`,

`session.serialize_handler`, `session.use_only_cookies` from the interpreter

**and** from the ini files, FPM pools and web server configuration on disk.

The CLI ini is not the web ini, so both are read.

* `session.upload_progress.enabled=0` is only a mitigation when it is set in the

configuration that applies before request parsing; a `.user.ini` value is

* `session.serialize_handler` mismatch between files that one

`session.save_path` (the configuration half of the session primitive).

* Session directory scan for artefacts of the content injection: a `sess_*`

file that carries `upload_progress_` to a PHP open tag is **critical**

(poisoned session file), a bare marker is an attempt indicator, and an

aborted serialization is the write-denial primitive.

* Document root scan for artefacts of the content injection and for PHP

configuration. The roots come from the `DocumentRoot` and `root` directives of

the Apache and Nginx configuration; `--webroot` adds one more. Files carrying

the `upload_progress_` marker are reported, and `.htaccess`/`*.ini` files are

read for the same directives as the system configuration.

* `session.upload_progress.cleanup` decides whether an arming attempt leaves a

file behind: with cleanup off the progress entry stays in the session file,

with cleanup on it is removed when the request ends and a later session

* Post-exploitation tell: a PHP or web server process with a shell as child.

The scanner was verified against the offline lab in

[mikensec/php2shell-lab]( and against

### Running the local mode inside a container

`local` scans the machine it runs on, so point it at a container by putting the

binary in it. Build on the host, copy, run:

# 1. build for linux (native arch of this host)

For a container with the other architecture:

GOOS=linux GOARCH=amd64 go build -o dist/p2s-amd64 . # x86_64 container

GOOS=linux GOARCH=arm64 go build -o dist/p2s-arm64 . # aarch64 container

Copy to `/usr/local/bin`, not `/tmp`: the lab victim mounts `/tmp` as tmpfs, so

a file copied there disappears. Add `--webroot` only when the document root is

# in the lab: make the victim vulnerable (the lab defaults to a floating tag)

cp .env.example .env # pins PHP_TAG=8.3.35-apache

./lab.sh version # -> [VULNERABLE] 8.3.35 /index.php

`lab/` holds a throwaway SSH target (PHP 8.3.35 with an SSH server) so the `ssh`

mode can be tested without touching a real host:

ssh-keygen -t ed25519 -f ./id_ed25519 -N ''

ssh-keyscan -p 2222 127.0.0.1 >> ./known_hosts

../php2shell-scanner ssh -H 127.0.0.1 -p 2222 -U root \

-i ./id_ed25519 --known-hosts ./known_hosts --webroot /var/www/html

The blackbox arming probe and the host-side session scan are two halves of the

same check: the probe writes a `sess_` file into the session

directory, and the host scan finds it. With `--canary` the marker contains an

inert `` , which is what makes the Suricata rule

`30260002` and the YARA rule `PHP_UploadProgress_Session_Poison` match.

* The lab pins `PHP_TAG=8.3.35-apache` in `.env`. Without that pin the compose

file resolves a floating tag, which is already patched (8.3.36) and reports

* `session.upload_progress.enabled=0` closes the confirmed session paths only.

`memory_limit=-1` removes the predictable memory-limit failure for the OOM

path. Neither is a substitute for the patch.

* The version table is the one from the PHP advisories of 2026-10-21. Confirm

the range against php.net before acting on an end-of-life branch.

Go 1.27+. One dependency: `golang.org/x/crypto/ssh` (SSH transport only).

`.github/workflows/release.yml` builds the release binaries. It runs when a

release is published, and it can also be started by hand with

`workflow_dispatch` and a tag, in which case it creates the release when it