#waf

1 post.

Dealing with php2shell

/
Requests passing through a WAF to PHP, with exploit attempts blocked

When John Blackbourn alerted the team about the leaked PHP vulnerability, I knew where to make the recommended configuration change. But I also knew that rolling it out across our infrastructure would take time.

That was on October 21, 2026. The vulnerability could allow remote code execution without authentication. It was later disclosed as CVE-2026-109618 and CVE-2026-109619, and became known as php2shell. The disclosure had been moved forward by a day, and a proof of concept was already circulating.

We didn’t have the complete picture yet.

Working with incomplete information

The leak pointed to PHP’s session upload-progress feature. One of the recommendations was to disable session.upload_progress.enabled.

The official PHP security releases weren’t scheduled until 14:00 UTC, more than ten hours away. Upgrading wasn’t an option yet. We needed to reduce our exposure until then.

Our infrastructure uses shared base images and a controlled release process, so even a small config change goes through several steps before it reaches production. I estimated around two hours (took longer).

With a proof of concept already out there, I started thinking about what else we could do in the meantime.

A faster mitigation at the WAF level

I suggested a platform-wide WAF rule to block requests matching the known exploit pattern. A WAF rule can be deployed without touching the application runtime, so we wouldn’t have to wait for every environment to get the PHP change.

This was not a small change. A WAF rule at the platform level sits in front of every site we host. If I got it wrong, it wouldn’t break one site. It could break all of them at once.

So instead of blocking a whole class of requests, which could break normal WordPress functionality, we targeted the specific pattern we understood.

I tested it in staging. It blocked the exploit requests while file uploads and editor saves kept working. But staging is not production. I tested as much as I could, and there was still no way to be sure it wouldn’t block something real.

I also knew it could be bypassed. It was never meant to be the fix, just something to reduce exposure while we waited.

Waiting felt riskier than shipping, so I made the call and rolled it out to production.

Watching the traffic

I checked the WAF logs every few minutes for blocked requests and possible false positives. My own test requests were blocked first. Later, real traffic matching the pattern started showing up, and the WAF was blocking it.

That was encouraging, but a blocked request doesn’t mean an exploit was prevented. It only meant requests matching our rule were stopped. At that point, I could say our mitigation was working against the pattern we knew about. I couldn’t say the platform was fully protected.

Meanwhile, the PHP change

The image builds ran into a problem I didn’t expect. An external service we use to install PHP extensions was having availability issues, and builds failed intermittently. A simple config change, delayed by something outside our control.

After retries, the builds succeeded and we verified the setting:

session.upload_progress.enabled => Off

Then it rolled out through our normal deployment process. Unlike the WAF rule, this disabled the affected feature itself.

It also made me think about our release process. Pinning dependencies and runtime versions makes deployments reproducible, which is usually a good thing. But during an incident, every extra step is time between a fix existing and that fix reaching production.

What the advisory revealed

At 14:00 UTC, the PHP team published the full disclosure and fixes. There were two critical vulnerabilities, both rated 9.2 under CVSS v4.0:

  • CVE-2026-109619: RCE involving stale global state. Disabling session.upload_progress.enabled was one of the recommended temporary mitigations.
  • CVE-2026-109618: RCE involving out-of-memory corruption. We didn’t know about this one from the leak, and nothing we had done addressed it.

The advisory was clear:

There are no complete mitigations, updating is the only safe choice.

The fixes shipped in PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35, and the team upgraded PHP across all customer environments. That’s what actually resolved it.

We acted on what we knew, but it turned out we only knew half of it. I’m glad we had a way to add a layer of protection fast, and that we never treated it as the fix.

A mitigation doesn’t have to be perfect to be useful, but you need to know its limits.