Last week was the week the world realised that the security of much of the web depends on a handful of maintainers, including one Ondřej Surý.

In this article, I explain how taking PHP out of the equation allowed me to keep all my sites online and safe, and wait out the storm.

Modern php depends on ondrej Based on the "Dependency" comic by XKCD

Everybody Panic #

It’s a Tuesday evening. Everything is fine. The PHP security team publishes an advance notice of an upcoming security release. Just 18 hours before the scheduled release!!

Apparently an out-of-memory problem discovered in 2024 but never resolved is being exploited by none-other-than AI, resulting in a critical vulnerability in all latest versions of PHP.

The announcement says: 

All PHP users are encouraged to prepare now so they can upgrade as soon as the release is available. 

😬

We reserve the right to release earlier should details of either vulnerability become public, or should evidence of active exploitation appear in the wild.

😭

Preparing for the unpreparable #

I SSH into the servers I manage, and run sudo apt update && sudo apt upgrade to update all packages to their latest versions. 

It’s all I can do to ​“prepare”.

Of course, PHP stays at its current version. The patches haven’t been released yet.

Taking sites down #

At least ten countries recommend taking vulnerable PHP systems offline if they can’t be patched promptly. 

Parts of Wikipedia are taken offline.

Taking PHP out of the equation #

I have a strong feeling that, even when the security update was released, it’ll take time to become available. The update will have to be built and packaged for each minor version of PHP, for each OS, for each architecture. And all the while, everybody and their mother will be scrambling to get it.

So instead of entering the royal rumble, I decide to stop PHP-FPM, let Nginx serve static pages cached by Blitz, and wait it out.

Before doing so, I add a simple conditional to every Twig template that contains dynamic content, or that sends a POST request to the server.

{% if isSiteStatic %}
    Form is currently unavailable. Please try again later.
{% else %}
    <form>...</form>
{% endif %}

I register isSiteStatic as a Twig global variable (not a Craft global set) as part of a custom module.

use Craft;
use craft\helpers\App;
use craft\web\View;

Craft::$app->onInit(function() {
    Craft::$app->getView()
        ->getTwig(View::TEMPLATE_MODE_SITE)
        ->addGlobal('isSiteStatic', App::env('IS_SITE_STATIC') ?? false);
});

And now I can enable/​disable isSiteStatic using an environment variable.

IS_SITE_STATIC=1

Once this is all in place, I refresh the Blitz cache to generate the static version of the site. I also run Cache Igniter to ensure the cache is warm globally.

Using ​“Blitz File Storage” with server rewrites allows Nginx to serve cached files directly, without invoking PHP. Which is exactly what we want.

The final step is to stop PHP-FPM. Nginx continues serving static pages cached by Blitz, and I am done.

Aftermath #

The following morning, patched PHP packages became available. I updated my servers, restarted PHP-FPM, disabled isSiteStatic, refreshed the Blitz cache, and everything returned to normal.

The PHP Foundation published a statement acknowledging shortcomings in PHP’s security processes and outlining plans to address them.

They are now calling on businesses, hosting providers and governments that rely on PHP to help fund the work required to keep it secure.

Meanwhile, PHP2Shell made headlines. The Wall Street Journal covered the vulnerabilities in an exclusive, with multiple follow-up articles. Other mainstream media picked the story up too, including The Atlantic’s If Anything Happens to This Guy … Everyone Is Screwed.

Caching is usually sold as a performance optimisation. Last week, it became a security strategy.

A tweet by Justen Holter