We Thought We Had No PHP. We Were Wrong.
The first thing we did when the php2shell advisory landed was make the mistake almost everyone makes.
We checked the websites.
That sounds responsible until you remember what php2shell actually is. CVE-2026-109618 and CVE-2026-109619 are critical unauthenticated remote code execution flaws in PHP itself. The vulnerable surface is not one plug-in, one theme, or one badly written upload form. It is the PHP handler sitting between the web server and the code. Apache, nginx, LiteSpeed, FPM, mod_php: if PHP is in the request path and it is not patched, the details of your application may not save you.
So the better question was not, "Do we run a PHP site?"
It was, "Where can PHP execute anywhere on these machines?"
That is where it got uncomfortable.
The box we almost waved through
We run cl0q, a search engine and index of the public web. Our own stack is mostly Go, Postgres, static files, and the usual operational scar tissue. From the outside, our production web box did not look like a PHP target. Probing the public site did not show PHP fingerprints. It would have been easy to write "not affected" in the team channel and move on.
Then the on-box check told a different story.
PHP-FPM was installed and active, wired behind Apache for cl0q and other virtual hosts. Nothing about the public pages screamed PHP, but the handler was there, alive, and able to process requests routed to it. We patched the PHP 8.3 packages to the Ubuntu security backport and restarted FPM. Then we checked the sites again.
That should have been the end of it. It was not.
Our second machine, the one that hosts our database, Gitea, and team chat, was worse. Apache had mod_php loaded. Not installed in a corner. Loaded. Of all the variants in the advisory, mod_php is the one you least want to discover by accident, because PHP runs inside the web server process itself.
We upgraded the PHP 8.1 packages to the jammy-security build and restarted Apache on the patched module.
Two machines. Two different PHP exposure paths. Neither one was obvious from the website.
"But the site does not use PHP"
This is the sentence that gets people hurt during a disclosure like this.
A modern server is not a single app with a tidy label. It is a pile of decisions made over months: a package installed for one test, a handler left enabled after a migration, a default virtual host nobody remembers, a legacy tool that still gets one request a week. Attackers do not care which parts were intentional. They care which parts execute.
That is also why configuration tricks are such a poor comfort blanket here. The guidance around php2shell has been blunt: do not talk yourself into safety with a clever rule or a partial workaround. The fix is the patched PHP release. Upstream fixed versions are PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35, with distribution backports doing the same work under different version strings.
Patch the interpreter. Restart the service. Then prove the public site still works.
What the wider web looks like from here
cl0q currently knows about 46.8 million domains and 33.7 million indexed pages. Numbers that large stop feeling real until a day like this turns them into a checklist. Millions of sites advertise Apache, nginx, or LiteSpeed in their server banners. A banner does not prove PHP is present, and it certainly does not prove a host is vulnerable. That distinction matters.
But a banner does tell you where to look first.
That is the practical use of an index like ours during a fast-moving disclosure. You can search and browse the public web by the technology a site exposes, follow a domain into its dossier, and see the shape of its stack before you start asking harder questions. Start with cl0q Explore if you want the wide view. Or pick a known stack and inspect it, the way this LiteSpeed dossier shows what we know about one public site.
None of that replaces checking your own machines. It does stop you from guessing blind.
The checklist we are using now
If you run anything with a web server, steal this order of operations:
- Inventory the machine, not the brand of the website. List web servers, PHP packages, FPM services, Apache modules, containers, and old virtual hosts.
- Ask where PHP can execute. A package that is merely installed is different from a handler that is loaded or proxied, but you need evidence for either answer.
- Patch PHP from your distribution or upstream. Do not settle for a workaround when a fixed build exists.
- Restart the actual service that loaded the old code. FPM, Apache, and containers do not magically pick up a patched library by wishing.
- Retest from outside and inside. Outside shows what the internet sees. Inside catches the quiet handler that never made it into the brochure.
- Write down what you found, even if the answer is boring. Future you will not remember which box had mod_php loaded at 2 a.m.
The uncomfortable takeaway
The scariest part of php2shell for us was not the severity, although unauthenticated RCE is about as bad as a Tuesday gets. It was the gap between the system we thought we were running and the system that was actually switched on.
That gap is normal. It is also measurable.
We built cl0q to make the public web easier to inspect: what a domain runs, where it lives, what changed, and how the pieces connect. Days like this are why that matters. Check your own stack first, patch it, and then come look at the rest of the internet with us.
Start with Explore. If you need the data programmatically, the cl0q API pricing page has the live tiers, including the free starting point and paid plans for higher limits.
And if your inventory starts with "we do not run PHP," go look anyway.
We said that too.
Top comments (0)