When PHP2Shell landed on 21 October, we built a free scanner so other teams could find every vulnerable PHP they run. It would be odd to publish that and not point it at ourselves. So we did. This is what we checked, what we found, and what is still open — including the parts that don't flatter us.
Where PHP lives in our platform
The part of AdZoic that decides each bid is written in Go and has no PHP in it, so PHP2Shell can't reach the auction itself. PHP runs our dashboards: the Laravel application advertisers and agencies use to plan campaigns and read reports, served by PHP-FPM and shipped as a container image.
We started with the main dashboard. It holds campaign budgets and reports, so it is exactly what an attacker would want.
Step 1: the recipe
A container image is built from a recipe, its Dockerfile. Ours names the PHP base image, and the team pinned it to PHP 8.4.27, the patched release, the day after the advisory.
Reading a Dockerfile is not proof, so we built an image with exactly the production PHP layers — the same base, our production settings file and our PHP-FPM pool file — and scanned it with php2shell-check. Both the command-line PHP and PHP-FPM came back PATCHED.
Step 2: the settings
Two days later PHP warned of a second, unpatched issue, CVE-2026-115560. It depends on settings, not versions, so we checked those next:
- The session format is the default. Our settings never change
session.serialize_handler, so it staysphp, which rules the issue out on its own. - Our code never takes the risky path. Laravel keeps sessions in its own store and never calls PHP's
session_start(). We searched our code and every installed dependency for the pattern PHP describes and found nothing.
The scanner agreed: NOT_EXPOSED for both. PHP-FPM's own settings dump said the same.
Step 3: what we found
The scanner also looked at images already built on our machines, and one of them was not patched. A developer's laptop was still running a local copy of the dashboard built in September, on PHP 8.4.26 — one release short of the fix. It had been up for almost two days.
It was a development copy, not production. But it is the lesson of this whole episode in one container: pinning the recipe fixes the next build, not the ones you already have. Every image built before the change keeps the old PHP until someone rebuilds it — on laptops, in test environments, anywhere. The fix is a rebuild.
A patch in a Dockerfile is a promise about the next build. Scan the images you actually have — and the ones actually running — not just the recipe.
Step 4: one line of extra safety
We are not exposed to CVE-2026-115560 today, but PHP's advice is to turn session upload progress off until its next release, and our dashboard doesn't use upload progress. So we turned it off in production settings:
session.upload_progress.enabled = Off
Before calling it done, we checked it three ways: PHP-FPM reports the setting Off, PHP-FPM's own configuration test passes when run as the same non-root user as production, and the scanner reports the image as safe. The change is in review and ships with the next production image.
What is still open
Everything above checks what we build. The last step is the one that matters most: scanning the image that is actually running in production, not the recipe for it. That check uses the same scanner pointed at the deployed image. We are also confirming that no older PHP service is still running anywhere: a PHP branch past its end of life gets no fix for PHP2Shell at all. We'll update this post with both results.
If you run PHP, do the same
- Patch the recipe: PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35 or later, or your Linux vendor's patched package.
- Rebuild every image, including the ones on developer machines and in test environments.
- Restart PHP-FPM and your web servers, so no worker keeps running the old program.
- Check the session settings for every PHP and every PHP-FPM pool, or set upload progress off.
- Scan what is actually running, not just what you meant to ship.
php2shell-check does steps 3 to 5 in one command, for free.
We describe our setup in general terms only. We leave out hostnames, image names, registries, internal repositories and anything else that would help an attacker. The checks were run on 25 October 2026.

