Incident Response WordPress PHP

Is PHP Running Correctly Between Your Host and Your WordPress Install?

A two-week-old "verified" PHP upgrade never actually happened — and only a plugin banner told the truth. Here's the full diagnosis, and how to check your own site in five minutes.
AIMF Security Blog • October 24, 2026 • 12 min read • Part 1 of the php2shell series

The dashboard's version and the server's version are two different facts. Only one of them is load-bearing.

9 hrs
Cloudflare WAF rules before public disclosure
4 days
Exploit attempts before the patch existed
2 weeks
Our "verified" upgrade that never happened
5 min
To check your own site
Short answer: On managed hosting, the PHP version shown in your hosting dashboard is a setting, not a fact. The version your site actually executes can be days or weeks behind it — and during an active exploitation wave like the October 2026 "php2shell" disclosures, that gap is exactly where attackers live. The only reliable check is reading the version from the running PHP worker itself — and WordPress shows you that for free in Site Health.

The week everything was on fire

On October 21, 2026, the PHP project shipped emergency fixes for two critical vulnerabilities — CVE-2026-109618 and CVE-2026-109619, collectively dubbed "php2shell." Both allow an unauthenticated attacker to execute code on a web server by sending a specially crafted request. The bug lives in the PHP engine itself, which means it fires before WordPress, before your security plugins, before anything you control. The only complete fix is a patched PHP build on the hosting server.

Cloudflare deployed WAF protections nine hours before public disclosure. Government sites went offline across Europe as a precaution. Exploit attempts were hitting honeypots four days before the patch existed. If you run a WordPress site, this was the week to know — precisely — what PHP build your server was executing.

We thought we knew. We had screenshots.

The banner we dismissed

Two weeks earlier, we had switched both of our sites to PHP 8.3 through the hosting dashboard — and verified it, with screenshots, at the time. So when a yellow banner appeared in the WordPress admin reading "Your website is running an outdated version of PHP (8.1.34)," we did what experienced admins do: we assumed the banner was stale. Plugins persist those notices; surely it was generated before the upgrade and never cleared.

That assumption was backwards. The banner wasn't stale. It was the only component in the entire stack reading the live runtime — and it was telling the truth while every dashboard in the chain lied.

WordPress Site Health warning banner: running an older version of PHP (8.1.34)

The banner we almost dismissed — the only component in the stack reading the live runtime.

The diagnosis: three layers, two truths

Here's the structure that made this bug so hard to see. Bluehost shows PHP versions in two places — and neither one reads the runtime:

  1. The Bluehost dashboard (Websites → [site] → Advanced → PHP Version) — shows the branch only ("8.3"), and what it's actually showing is your intent, cached.
  2. cPanel → MultiPHP Manager — the hidden, more precise one: a per-domain table naming the actual package ("PHP 8.3 (ea-php83)"). Still a setting.
  3. WordPress → Tools → Site Health → Info → Server — the only layer that reads the PHP worker executing the request. This is the ground truth.
Bluehost's two PHP version views side by side: the hosting dashboard and cPanel MultiPHP Manager

Two places, two settings — neither reads the runtime.

In our case: the dashboard said 8.3. MultiPHP Manager said 8.3. Site Health said 8.1.34. Three interfaces, two realities.

The smoking gun: one line in .htaccess

On LiteSpeed shared hosting, the PHP build that actually serves your site is chosen by a single machine-generated line at the bottom of your site's .htaccess:

# php -- BEGIN cPanel-generated handler, do not edit
# Set the "ea-php81" package as the default "PHP" programming language.

  AddHandler application/x-httpd-ea-php81___lsphp .php .php8 .phtml

# php -- END cPanel-generated handler, do not edit

That block is written by cPanel itself — not by us, not by any plugin. And the forensics were damning: the hosting platform's own PHP upgrader leaves snapshot files behind at every version change (.htaccess.phpupgrader.*). Ours showed the handler's complete history: PHP 8.0 → PHP 8.1. Never 8.3. The October 14 upgrade had updated the dashboard's state — and never touched the one line that decides what executes.

The .htaccess PHP handler before and after: ea-php81 changed to ea-php83

The smoking gun: one cPanel-generated line decides what executes.

The fix — and the trap inside it

The fix itself is one word in one line: change ea-php81 to ea-php83 in the handler. But the road there had two potholes worth documenting:

Trap 1 — the re-apply that didn't apply. Even a support agent re-applying PHP 8.3 through the official tooling didn't rewrite the handler. The dashboard confirmed success. The runtime didn't move. If a settings change reports success, verify it against the runtime before you believe it — every time.
Trap 2 — the 500-error paste. When we edited the file, we pasted the new content from a notes document — including the markdown formatting around it. Prose and code-fence lines aren't valid .htaccess directives: the site returned 500 Internal Server Error on every PHP request within seconds. The rollback "failed" for the same reason. The fix: paste only the raw directives, from a plain-text file whose last line is a real directive.

After the clean one-line edit: Site Health read 8.3.36 — the patched build — and the opcode cache statistics (fresh hit rate, newly allocated memory) confirmed the workers had reloaded with the new binary.

WordPress Site Health Info panel showing PHP version 8.3.36 with SAPI litespeed

Ground truth after the fix: 8.3.36, the patched build.

The verification hierarchy (pin this)

SourceWhat it tells youTrust
Hosting dashboardIntent / cached settingCan be wrong — proven here
cPanel MultiPHP ManagerPer-domain setting + package nameBetter — still a setting
.htaccess handler lineWhat the server is configured to loadStrong — read it directly
WordPress Site Health (Info → Server)What is executing right nowGround truth
Host support statementsTheir tools' view — which may be the lying UIVerify against runtime

Check your own site in five minutes

  1. WordPress → Tools → Site Health → Info → Server — read the PHP version. Note the SAPI row too (litespeed and mod_php mean persistent workers — the riskiest model for state-reuse bugs).
  2. Compare against the fixed builds for the current PHP vulnerabilities. For php2shell: 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12. If your branch shows the fixed build or higher, you're patched. If it shows anything lower — including a version your dashboard swears is current — you have the lie.
  3. If they disagree: check the handler line at the bottom of your site's .htaccess (cPanel → File Manager → show hidden files). If it names an older package than your dashboard claims, that's your divergence. Fix the line (backup first), re-verify in Site Health.
  4. Ask your host the questions that work: "What is the exact package build serving my domain — not the branch?" and "Was the handler in my .htaccess rewritten when I changed versions?" Expect honesty about limits: on shared hosting, patches apply server-wide and per-account confirmation generally isn't offered — which is exactly why the runtime check is yours to run.

What this incident actually taught

The scariest part of php2shell wasn't the vulnerability — it was the verification gap. A patched engine is the only complete fix, and the entire tooling chain between you and that engine can report success without delivering it. The plugin banner we almost dismissed knew more than the hosting dashboard, the support agent, and two weeks of our own screenshots.

Trust the runtime. Verify everything else.

Questions this article answers

Does the hosting dashboard show the exact PHP patch version?

No — it shows the branch (e.g. "8.3") and reflects your setting, not the runtime. The exact build (e.g. 8.3.36) is visible in WordPress Site Health.

How do I check what PHP version my site is actually running?

WordPress → Tools → Site Health → Info → Server → PHP version. It reads the executing worker, not a settings database.

Why did my PHP upgrade not take effect?

On LiteSpeed/cPanel shared hosting, the executed build is controlled by the cPanel-generated handler line at the bottom of your site's .htaccess. If the upgrade tooling didn't rewrite that line, the dashboard updates while the runtime doesn't.

What PHP versions fix the php2shell vulnerabilities (CVE-2026-109618 / CVE-2026-109619)?

PHP 8.2.35, 8.3.36, 8.4.27, and 8.5.12, released October 21, 2026. Older branches have no official fix — ask your host about backports.

Can a WordPress security plugin fix php2shell?

No. The bug is in the PHP engine, exploited before WordPress loads. Only the patched PHP build fixes it; a WAF reduces exposure.

Next in the series: Part 2 — hardening Cloudflare in front of WordPress: the four-layer stack, and the two configuration traps that produced rules which validated, deployed, showed Active — and matched nothing.

Sources

  1. [TO ADD: PHP 8.3.36 release announcement — php.net]
  2. [TO ADD: NVD entry — CVE-2026-109618]
  3. [TO ADD: NVD entry — CVE-2026-109619]
  4. [TO ADD: Cloudflare WAF protection timeline — Cloudflare blog]
  5. [TO ADD: WordPress Site Health PHP-version check documentation — wordpress.org]