Journal

A critical PHP vulnerability dropped. Here is what patch day looked like.

Remote code execution in the runtime under every site we host, patched the same morning. The inventory, the two-track decision, and what we automated so next time needs nobody.

A critical vulnerability in PHP itself was published on a weekday morning in October. Not a plugin, not a framework: the language runtime under every WordPress and Magento site we host. Remote code execution, trivially exploitable, with a proof of concept circulating within hours. Here is what patch day looked like across every server we run, and the decisions behind it.

The first hour: scope

Before touching anything we listed every server, every PHP version on it and every site using each version. That list already existed, which is the first thing to say about patch day: it goes well or badly depending on work done months earlier. Each site runs its own PHP-FPM pool, so the inventory was a one-line query per server rather than an archaeology project.

The vulnerable versions covered every box. Some sites could move to the newest PHP release, which carried the fix. Others could not, because the application pinned an older major version, and for those the fix was a patched release of the version they were already on.

The decision: two tracks

WordPress sites went to the current PHP release. Modern WordPress and the plugins we allow on our platforms run cleanly on it, and staying on an old major version just means a bigger jump next time. Each site was tested on its staging copy first, then switched in production with a pool restart that took under a second of downtime.

Magento stores stayed on their supported major version and took the patched point release. Magento certifies specific PHP versions per release, and moving a store to an uncertified runtime to fix one vulnerability trades a known risk for an unknown one. The patched release closed the hole without changing anything the application cares about.

One store runs in containers behind a proxy, and the container image had to be rebuilt rather than the host updated. Same decision, different mechanics.

Making it stick

Patching once is a sprint. The useful outcome is that the next critical runtime vulnerability does not need a human on a weekday morning. Every server now runs an unattended security-update job for the runtime packages, with the application-specific pins respected, so a patched point release lands overnight on its own. The container build is on the same schedule. Each site’s monitoring confirms the pool came back after the restart, so an upgrade that fails to start is noticed within minutes rather than at the next customer complaint.

What we checked afterwards

  • Every site returned the expected PHP version, confirmed from the running process rather than from the package list, because the two can disagree if a pool was not restarted.
  • Every checkout, login and contact form was exercised on the upgraded sites. A runtime upgrade that breaks one extension breaks it quietly.
  • Every server’s logs for the hours between disclosure and patch were reviewed for the exploit signature. Nothing had been attempted, which is luck as much as anything, and luck is not a plan.

The honest lesson

Patch day was a few hours because the groundwork was done: one pool per site, staging copies that matched production, an inventory that was already true, and a rule that application code is never modified so that upgrades do not collide with local hacks. Without those, the same vulnerability is a week of fear and a few sites that never quite get patched.

If you do not know what PHP version your site runs, or who would patch it on a morning like that one, that is the gap we close when we take over a platform.

Got a similar problem? Talk to us.

Start a project