Skip to content

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.

You need Node.js 22 or later. Point the probe at any page PHP serves:

Terminal window
npx @keygraph/shannon@3.5.0 start --exp-php2shell -u https://staging.example.com

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

  • -y skips 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 to php2shell-report.json in that directory.

A crash is the main signal, and the version in PHP’s response header is the fallback. Upgrade whatever the verdict says.

VerdictWhat it sawWhat to do
DetectedA worker crashed, or the version header reports a release below the fixTreat 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-patchedNo crash, and the header reports a fixed releaseThe 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-signalNo crash was observed, and no version could be readThe weakest result. With expose_php and display_errors both off, a crash leaves nothing visible from outside. Run php -v on the server
InconclusiveThe request was blocked before PHP, the first request got no answer or a server error, or the deadline passedNothing 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

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:

Terminal window
npx @keygraph/shannon@3.5.0 start --exp-php2shell -u "$STAGING_URL" -y -o php2shell
jq -e '.verdict == "not_detected" and .signal == "version-patched"' php2shell/php2shell-report.json

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

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_size or Apache LimitRequestBody
  • A WAF rule on the number or size of multipart parts
  • PHP’s own max_multipart_body_parts and max_input_vars, kept at conservative values

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.