Verify that your PHP upgrade landed
After upgrading PHP, run this check against a staging copy of the server. It sends probe requests designed to crash a PHP worker on an affected release, then reports what it observed. It runs in the CLI alone, with no Docker, model key, or source code.
Upgrade first. The fixed upstream releases are 8.2.35, 8.3.36, 8.4.27, and 8.5.12; earlier upstream releases on those branches are affected. PHP 8.1 and earlier are out of upstream support, so upgrade to a supported branch. Some vendors provide backported fixes, and the PHP security team has provided minimal source backports. If you rely on either, check the relevant security advisory to confirm the fix rather than judging by the version number alone. See the PHP security advisory for current guidance.
The flaw is one of two in the PHP interpreter, known together as php2shell and disclosed on 21 October 2026. Together they allow unauthenticated remote code execution against any reachable PHP endpoint. This probe covers the first, CVE-2026-109618, and it stops there. It never attempts the second step.
Run the check
Section titled “Run the check”You need Node.js 22 or later. Point the probe at any page PHP serves:
npx @keygraph/shannon@3.5.0 start --exp-php2shell -u https://staging.example.comNothing is sent until you confirm the prompt. One reachable PHP endpoint is enough, because the flaw is in the interpreter. To test another path or server, run the command again with that URL.
Two flags are useful outside a terminal session:
-yskips the confirmation. Without it, a job with no terminal stops with an error and exit code 1 before anything is sent.-o <dir>writes the result tophp2shell-report.jsonin that directory.
Read the result
Section titled “Read the result”A crash is the main signal, and the version in PHP’s response header is the fallback. Upgrade whatever the verdict says.
| Verdict | What it saw | What to do |
|---|---|---|
| Detected | A worker crashed, or the version header reports a release below the fix | Treat the server as affected. Check the upgrade reached this server. On Debian, Ubuntu, or RHEL, read the distribution’s advisory first, because a backported fix keeps the old version number |
Not detected, signal version-patched | No crash, and the header reports a fixed release | The strongest result this probe provides: the endpoint reports a fixed PHP version and no crash was observed. This is not proof that the application is secure. |
Not detected, signal no-observable-signal | No crash was observed, and no version could be read | The weakest result. With expose_php and display_errors both off, a crash leaves nothing visible from outside. Run php -v on the server |
| Inconclusive | The request was blocked before PHP, the first request got no answer or a server error, or the deadline passed | Nothing is known yet. Behind nginx, the default client_max_body_size of 1 MiB returns a 413 and gives this verdict. Test closer to PHP or raise the limit on staging |
Use it in a pipeline
Section titled “Use it in a pipeline”The command exits 1 on Detected and 0 on the other two verdicts, so exit code 0 can still mean Inconclusive. To pass a job only on the strongest result, read the report as well:
npx @keygraph/shannon@3.5.0 start --exp-php2shell -u "$STAGING_URL" -y -o php2shelljq -e '.verdict == "not_detected" and .signal == "version-patched"' php2shell/php2shell-report.jsonA server with expose_php off can’t reach that result, so check php -v in the job there. Keep $STAGING_URL on a server you’re authorized to crash, since -y removes the prompt.
If you can’t upgrade today
Section titled “If you can’t upgrade today”These limits stop the same request shape at the edge. They help while you schedule the upgrade, and they don’t replace it, because each one is a bet on catching every variant of the payload:
- A request body limit in the reverse proxy: nginx
client_max_body_sizeor ApacheLimitRequestBody - A WAF rule on the number or size of multipart parts
- PHP’s own
max_multipart_body_partsandmax_input_vars, kept at conservative values
What the probe was tested on
Section titled “What the probe was tested on”The validation matrix ran 10 local Docker targets on 26 October 2026: the CLI server, PHP-FPM behind nginx, Apache mod_php, patched and unpatched 8.3 and 8.4, end-of-life 8.1, a third-party vulnerable app, and two non-PHP controls. Testing used two passes: the shipped CLI using all available signals, and a crash-only module harness with version headers stripped before classification. All 20 checks gave the expected verdict, with no false positives on patched PHP or non-PHP targets in this matrix.
The matrix has no CDN or WAF in front of PHP, and no distribution-patched build. Expect Inconclusive or a backport mismatch there, as the table above describes.
The probe’s design draws on Assetnote’s php2shell scanner. Full details are in the probe’s repo docs.
This probe checks one crash vector associated with CVE-2026-109618. A Not detected result does not prove that the upgrade landed or that the application is secure. For broader application testing, run a full Shannon scan from the Quick Start.