php2shell: how we secured our servers

4 min read

On 21 October two critical flaws in the PHP core went public. The fix for our servers landed late that evening, carrying the same version number as before. What we did while we waited, and how we checked.

On 21 October 2026 two flaws in the core of PHP were disclosed. PHP is the language behind WordPress, Laravel and a large chunk of the web. The identifiers are CVE-2026-109618 and CVE-2026-109619 (a CVE is the reference code used to catalogue a vulnerability), and the internet quickly named them php2shell.

They score 9.2 on the CVSS 4.0 scale. Both allow remote code execution with no login required. Put simply, whoever exploits them can run the machine as if it were their own.

What happened that day

Official fixes shipped the same day: PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Real-world exploits were already circulating within hours, so waiting until morning was out of the question.

Cloudflare extended its WAF rules to free plans as well. A WAF is a web application firewall: it inspects requests before they reach the site. Cloudflare was candid about the limits, warning that attackers tweak their payloads to slip past the rules. The filter helps, but it is not enough on its own.

Our problem: no update yet

Our servers run CloudLinux with cPanel, which ships PHP in its own packages. On the evening of the 21st the patched versions of those packages had not been released.

We had five versions of PHP-FPM running, the service that executes PHP for each site: 7.4, 8.2, 8.3, 8.4 and 8.5. So we worked on two fronts.

1. Close the confirmed attack path

The confirmed attack path goes through session.upload_progress, a PHP feature that tracks the progress of file uploads. We switched it off on every installed version, using a dedicated config file and a PHP-FPM restart:

for d in /opt/cpanel/ea-php*/root/etc/php.d; do
  echo "session.upload_progress.enabled = 0" > $d/zz-php2shell.ini
done
/scripts/restartsrv_apache_php_fpm

The loop walks through every PHP version on the server, so none gets left behind. To be clear, this mitigation closes the confirmed attack path, not every possible one. It was a stopgap until the real fix arrived.

2. The filter in front

Our servers only accept web traffic that comes through Cloudflare. Anyone trying to hit a site by going straight to the server finds the door shut, which meant the new WAF rules applied to every single request.

A fix with the same version number

CloudLinux released the patched packages at 22:55 Italian time, with a detail that could easily mislead: the PHP version number did not change.

8.5.11 stayed 8.5.11, but the package became 8.5.11-2.el9.cloudlinux.1. Likewise 8.4.26 became 8.4.26-2…cloudlinux.1, and 7.4.33 moved to 7.4.33-18…cloudlinux.13.

If you only looked at the version number you saw 8.5.11 before and after, with no way of telling whether you were covered. You had to read the package revision.

The morning after

We updated the packages and checked the revisions on each server with one command:

rpm -qa 'ea-php*-php-cli' | sort

The rule was simple. Every line must end in .cloudlinux.1, and the 7.4 line in .cloudlinux.13. Anything else means that PHP still needs updating.

We left the mitigation file where it was. With the fix installed it works as a belt-and-braces measure, and it gets in the way of no site.

What we take away

Sources

field notes

Fresh from the workshop.

the next story

What's the next story?

Tell us what isn't working. The first meeting is to see whether it's worth doing anything, and if the answer is no, we'll tell you straight away.

Office
Via Vincenzo Arangio Ruiz 83, 80122 Napoli
The message that will be sent Pick something above…
Open WhatsApp with the message ready

Or call us: 351 240 7196 · [email protected]