riksi Start a project

PHP2Shell explained: check and fix your PHP in plain English

27 min read By

In October 2026, two serious bugs were found in PHP, the software engine behind most of the web. Together they were nicknamed PHP2Shell, and they let an attacker take over a website with no password. The one thing that matters: make sure the PHP running your site has the fix, then restart it. Updating WordPress, a theme or a plugin does not do this on its own. Below I explain what happened in plain words, and how to check and fix your own site. I’ve kept it to the concept, not a how-to for attackers.

You can skip to the version checker further down. But the story is worth knowing, because it shows how fast these things move.

The 30-second answer

PHP2Shell is the name for two critical flaws in PHP, tracked as CVE-2026-109618 and CVE-2026-109619. Under the right conditions, a stranger sending requests over the internet could make your server run their commands, without signing in. Both were rated 9.2 out of 10 for severity. That’s a danger score, not a 92% chance of being hacked.

Think of your website as a restaurant. WordPress is the menu and the ordering system. PHP is the kitchen that turns orders into finished meals. You can redesign the menu and change the manager’s password while the kitchen equipment still has a dangerous fault.

Split image: a tidy restaurant menu tagged WordPress, and a messy kitchen tagged PHP
WordPress is the menu. PHP is the kitchen. A nice menu can’t fix a broken kitchen.

Your first job is one question for whoever runs your server: does the PHP serving my site have the fix for both of those CVEs? Everything else below helps you ask it well and act on the answer.

What happened, and why it scared people

PHP is the engine behind about 69% of all websites, including WordPress. So a flaw in PHP itself is not one shop’s problem. It’s almost everyone’s problem at once.

The scary part of the warning was the phrase “unauthenticated remote code execution”. That means a stranger could run their own code on your server, without logging in. Think of someone opening your shop’s back office from the street. There was real urgency too. Amazon reported attempts against its decoy systems on 17 October, before any fix existed. Here’s how the days played out:

Timeline: 30 July audit, 17 October attempts seen, 20 October leak, 21 October patch, 22 October a defence broken
Attempts, a leak, a patch and a broken defence, in days.

How the bug worked, without the jargon

You don’t need to code to understand this. The whole thing comes down to one messy moment inside PHP and what got left behind afterwards.

<div class="ex" id="ex">
  <div class="head">
    <span class="step" id="step">1 / 8</span>
    <h3 id="title">Your website has layers</h3>
  </div>
  <div class="stage"><div class="art" id="art"></div></div>
  <div class="simple"><span class="slabel">THE SIMPLE VERSION</span><p id="say"></p></div>
  <div class="ctl">
    <button type="button" id="prev" aria-label="Previous step">←</button>
    <button type="button" id="play" class="pl">▶ Play</button>
    <button type="button" id="next" aria-label="Next step">→</button>
    <span class="dots" id="dots"></span>
  </div>
  <p class="foot">PHP2SHELL, in eight steps. A plain-English walkthrough, not a how-to.</p>
</div>
body { display: block; padding: 16px; place-items: normal; }
.ex { width: min(100%, 680px); margin: 0 auto; color: #171915; font-family: "DM Sans", system-ui, sans-serif; }
.head { display: flex; align-items: baseline; gap: 12px; margin-bottom: 12px; }
.step { font: 700 13px "Space Mono", ui-monospace, monospace; color: #5C5E57; letter-spacing: .08em; }
.head h3 { margin: 0; font: 800 22px/1.2 Manrope, sans-serif; }
.stage { position: relative; background: #F4F4F4; background-image: linear-gradient(#e8e8e6 1px, transparent 1px), linear-gradient(90deg, #e8e8e6 1px, transparent 1px); background-size: 34px 34px; border: 2px solid #E4E4E4; border-radius: 16px; padding: 26px 20px; min-height: 340px; display: flex; align-items: center; justify-content: center; overflow: hidden; }
.art { width: 100%; display: flex; align-items: center; justify-content: center; animation: fade .45s ease; }
@keyframes fade { from { opacity: 0; transform: translateY(10px); } }
.artsvg { width: min(100%, 380px); height: auto; display: block; }
.svgt { font-family: "Space Mono", ui-monospace, monospace; }
/* window mockup */
.win { width: min(100%, 440px); border: 2px solid #171915; border-radius: 12px; overflow: hidden; background: #fff; box-shadow: 0 12px 30px rgba(0,0,0,.08); }
.wbar { display: flex; justify-content: space-between; align-items: center; padding: 9px 14px; background: #FFD400; color: #171915; font: 700 14px "Space Mono", ui-monospace, monospace; letter-spacing: .5px; }
.wdots { font-size: 11px; opacity: .5; }
.wbody { padding: 18px 16px 20px; }
.wl { font: 700 14px "Space Mono", ui-monospace, monospace; letter-spacing: 1px; color: #5C5E57; margin-bottom: 12px; }
.mbar { height: 22px; border: 2px solid #171915; border-radius: 6px; overflow: hidden; margin-bottom: 16px; background: #F1F1F1; }
.mbar i { display: block; height: 100%; background: repeating-linear-gradient(90deg, #FFD400 0 12px, #e6c200 12px 15px); animation: grow .7s ease-out; }
@keyframes grow { from { width: 0 !important; } }
.cells { display: grid; grid-template-columns: repeat(6, 1fr); gap: 7px; }
.cell { height: 54px; border: 2px solid #D7D7D7; border-radius: 6px; display: flex; flex-direction: column; justify-content: center; gap: 5px; padding: 0 6px; background: #fff; }
.cell.fill { border-color: #171915; background: #FFF6BF; }
.cell b { height: 4px; background: #171915; border-radius: 2px; display: block; }
.cell b:nth-child(2) { width: 70%; opacity: .5; }
.tagrow { margin-top: 16px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.tg { display: inline-block; border: 2px solid; border-radius: 7px; padding: 5px 11px; font: 700 13.5px "Space Mono", ui-monospace, monospace; letter-spacing: .5px; }
.emptybox { width: 84px; height: 34px; border: 2px dashed #171915; border-radius: 6px; display: inline-block; }
.refwrap { position: relative; height: 150px; }
.note { position: absolute; left: 0; top: 24px; font-size: 19px; }
.note small { display: block; color: #5C5E57; font-size: 14px; margin-top: 3px; }
.arr { position: absolute; left: 96px; top: 0; width: 240px; height: 140px; }
.box { position: absolute; right: 18px; top: 70px; width: 86px; height: 66px; border: 3px solid #171915; border-radius: 9px; display: grid; place-items: center; font: 800 18px Manrope, sans-serif; color: #171915; background: #FFF6BF; transform: rotate(-2deg); }
.newbox { border-color: #D62828; color: #D62828; background: #fdecec; }
.flow { width: min(100%, 460px); display: flex; flex-direction: column; align-items: center; }
.fwin { width: 100%; border: 2px solid #D7D7D7; border-radius: 10px; overflow: hidden; background: #fff; }
.fbar { padding: 8px 13px; font: 700 13.5px "Space Mono", ui-monospace, monospace; letter-spacing: .5px; }
.fbody { padding: 14px; }
.muted { color: #5C5E57; font-size: 17px; }
.conn { color: #171915; font-size: 20px; line-height: 1; margin: 7px 0; }
.conn.x { color: #D62828; font-size: 26px; }
.patch { font-size: 18px; margin-bottom: 14px; line-height: 1.4; }
.patch:last-child { margin-bottom: 0; }
.patch b { color: #171915; background: #FFD400; border-radius: 5px; padding: 1px 8px; margin-right: 8px; font: 800 15px Manrope, sans-serif; }
.patch small { color: #5C5E57; font-size: 15px; }
.frow { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #E4E4E4; padding: 11px 2px; font-size: 20px; }
.frow b { font: 800 22px Manrope, sans-serif; background: #FFD400; border-radius: 6px; padding: 1px 11px; }
.fnote { margin-top: 14px; font-size: 16px; color: #5C5E57; }
.fnote.dim { color: #9A9C94; font-size: 14px; margin-top: 6px; }
.simple { border: 2px solid #D7D7D7; border-radius: 12px; padding: 16px 18px; background: #fff; margin-top: 14px; }
.slabel { font: 700 12px "Space Mono", ui-monospace, monospace; letter-spacing: 2px; color: #5C5E57; }
.simple p { margin: 8px 0 0; font-size: 17px; line-height: 1.5; }
.ctl { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
.ctl button { min-width: 44px; min-height: 44px; border: 2px solid #171915; border-radius: 999px; background: #fff; font: 700 15px "DM Sans", sans-serif; cursor: pointer; color: #171915; }
.ctl .pl { padding: 0 18px; background: #171915; color: #fff; }
.ctl button:focus-visible { outline: 3px solid #5B4AE8; outline-offset: 2px; }
.ctl button:disabled { opacity: .35; cursor: default; }
.dots { display: flex; gap: 6px; margin-left: 4px; flex-wrap: wrap; }
.dots i { width: 9px; height: 9px; border-radius: 50%; background: #D7D7D7; }
.dots i.on { background: #171915; }
.foot { margin: 12px 0 0; font-size: 12.5px; color: #5C5E57; }
@media (max-width: 560px) {
  body { padding: 10px; }
  .head h3 { font-size: 19px; }
  .stage { min-height: 300px; padding: 20px 14px; }
  .box { right: 6px; }
}
@media (prefers-reduced-motion: reduce) { .art, .mbar i { animation: none; } }
var $=function(id){return document.getElementById(id);};
var STEPS=[{"t": "Your website has layers", "art": "<svg viewBox=\"0 40 1080 900\" class=\"artsvg\"><g>\n      <rect x=\"470\" y=\"150\" width=\"140\" height=\"92\" rx=\"8\" fill=\"#fff\" stroke=\"#171915\" stroke-width=\"3\"/>\n      <line x1=\"492\" y1=\"178\" x2=\"568\" y2=\"178\" stroke=\"#171915\" stroke-width=\"3\"/>\n      <line x1=\"492\" y1=\"196\" x2=\"588\" y2=\"196\" stroke=\"#FFD400\" stroke-width=\"5\"/>\n      <line x1=\"492\" y1=\"214\" x2=\"556\" y2=\"214\" stroke=\"#171915\" stroke-width=\"3\"/>\n      <line x1=\"540\" y1=\"242\" x2=\"540\" y2=\"268\" stroke=\"#171915\" stroke-width=\"3\"/>\n      <polygon points=\"290,320 540,430 540,464 290,354\" fill=\"#EDEDED\" stroke=\"#171915\" stroke-width=\"2.5\"/><polygon points=\"790,320 540,430 540,464 790,354\" fill=\"#E2E2E2\" stroke=\"#171915\" stroke-width=\"2.5\"/><polygon points=\"290,320 540,210 790,320 540,430\" fill=\"#fff\" stroke=\"#171915\" stroke-width=\"3\"/><g transform=\"translate(300,466)\"><rect x=\"-6\" y=\"-26\" width=\"176\" height=\"34\" rx=\"6\" fill=\"#fff\" stroke=\"#171915\" stroke-width=\"2\"/><text x=\"6\" y=\"-3\" fill=\"#171915\" class=\"svgt\" font-size=\"17\" font-weight=\"700\">YOUR WEBSITE</text></g>\n      <polygon points=\"290,520 540,630 540,664 290,554\" fill=\"#EDEDED\" stroke=\"#171915\" stroke-width=\"2.5\"/><polygon points=\"790,520 540,630 540,664 790,554\" fill=\"#E2E2E2\" stroke=\"#171915\" stroke-width=\"2.5\"/><polygon points=\"290,520 540,410 790,520 540,630\" fill=\"#FFD400\" stroke=\"#171915\" stroke-width=\"3\"/><g transform=\"translate(360,666)\"><rect x=\"-6\" y=\"-26\" width=\"163\" height=\"34\" rx=\"6\" fill=\"#fff\" stroke=\"#171915\" stroke-width=\"2\"/><text x=\"6\" y=\"-3\" fill=\"#171915\" class=\"svgt\" font-size=\"17\" font-weight=\"700\">PHP RUNTIME</text></g>\n      <polygon points=\"290,720 540,830 540,864 290,754\" fill=\"#EDEDED\" stroke=\"#5C5E57\" stroke-width=\"2.5\"/><polygon points=\"790,720 540,830 540,864 790,754\" fill=\"#E2E2E2\" stroke=\"#5C5E57\" stroke-width=\"2.5\"/><polygon points=\"290,720 540,610 790,720 540,830\" fill=\"#EDEDED\" stroke=\"#5C5E57\" stroke-width=\"3\"/><g transform=\"translate(380,866)\"><rect x=\"-6\" y=\"-26\" width=\"98\" height=\"34\" rx=\"6\" fill=\"#fff\" stroke=\"#5C5E57\" stroke-width=\"2\"/><text x=\"6\" y=\"-3\" fill=\"#171915\" class=\"svgt\" font-size=\"17\" font-weight=\"700\">SERVER</text></g>\n      </g></svg>", "s": "A strong password can\u2019t repair a flaw in the engine underneath your website."}, {"t": "The crack was in PHP", "art": "<svg viewBox=\"0 40 1080 900\" class=\"artsvg\"><g>\n      <rect x=\"470\" y=\"150\" width=\"140\" height=\"92\" rx=\"8\" fill=\"#fff\" stroke=\"#171915\" stroke-width=\"3\"/>\n      <line x1=\"492\" y1=\"178\" x2=\"568\" y2=\"178\" stroke=\"#171915\" stroke-width=\"3\"/>\n      <line x1=\"492\" y1=\"196\" x2=\"588\" y2=\"196\" stroke=\"#FFD400\" stroke-width=\"5\"/>\n      <line x1=\"492\" y1=\"214\" x2=\"556\" y2=\"214\" stroke=\"#171915\" stroke-width=\"3\"/>\n      <line x1=\"540\" y1=\"242\" x2=\"540\" y2=\"268\" stroke=\"#171915\" stroke-width=\"3\"/>\n      <polygon points=\"290,320 540,430 540,464 290,354\" fill=\"#EDEDED\" stroke=\"#171915\" stroke-width=\"2.5\"/><polygon points=\"790,320 540,430 540,464 790,354\" fill=\"#E2E2E2\" stroke=\"#171915\" stroke-width=\"2.5\"/><polygon points=\"290,320 540,210 790,320 540,430\" fill=\"#fff\" stroke=\"#171915\" stroke-width=\"3\"/><g transform=\"translate(300,466)\"><rect x=\"-6\" y=\"-26\" width=\"176\" height=\"34\" rx=\"6\" fill=\"#fff\" stroke=\"#171915\" stroke-width=\"2\"/><text x=\"6\" y=\"-3\" fill=\"#171915\" class=\"svgt\" font-size=\"17\" font-weight=\"700\">YOUR WEBSITE</text></g>\n      <polygon points=\"290,520 540,630 540,664 290,554\" fill=\"#EDEDED\" stroke=\"#D62828\" stroke-width=\"2.5\"/><polygon points=\"790,520 540,630 540,664 790,554\" fill=\"#E2E2E2\" stroke=\"#D62828\" stroke-width=\"2.5\"/><polygon points=\"290,520 540,410 790,520 540,630\" fill=\"rgba(214,40,40,.10)\" stroke=\"#D62828\" stroke-width=\"3\"/><g transform=\"translate(360,666)\"><rect x=\"-6\" y=\"-26\" width=\"163\" height=\"34\" rx=\"6\" fill=\"#fff\" stroke=\"#D62828\" stroke-width=\"2\"/><text x=\"6\" y=\"-3\" fill=\"#171915\" class=\"svgt\" font-size=\"17\" font-weight=\"700\">PHP RUNTIME</text></g>\n      <polygon points=\"290,720 540,830 540,864 290,754\" fill=\"#EDEDED\" stroke=\"#5C5E57\" stroke-width=\"2.5\"/><polygon points=\"790,720 540,830 540,864 790,754\" fill=\"#E2E2E2\" stroke=\"#5C5E57\" stroke-width=\"2.5\"/><polygon points=\"290,720 540,610 790,720 540,830\" fill=\"#EDEDED\" stroke=\"#5C5E57\" stroke-width=\"3\"/><g transform=\"translate(380,866)\"><rect x=\"-6\" y=\"-26\" width=\"98\" height=\"34\" rx=\"6\" fill=\"#fff\" stroke=\"#5C5E57\" stroke-width=\"2\"/><text x=\"6\" y=\"-3\" fill=\"#171915\" class=\"svgt\" font-size=\"17\" font-weight=\"700\">SERVER</text></g>\n      <path d=\"M540 452 l-18 34 l24 14 l-20 36\" fill=\"none\" stroke=\"#D62828\" stroke-width=\"7\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></g></svg>", "s": "PHP2Shell was in the PHP runtime. Updating WordPress alone doesn\u2019t update that engine."}, {"t": "Bug 1: unfinished work", "art": "<div class=\"win\"><div class=\"wbar\"><span>PHP / TEMPORARY MEMORY</span><span class=\"wdots\">\u25a2 \u25a2 \u25a2</span></div><div class=\"wbody\"><div class=\"wl\">REQUEST MEMORY</div><div class=\"mbar\"><i style=\"width:78%\"></i></div><div class=\"cells\"><div class=\"cell fill\"><b></b><b></b></div><div class=\"cell fill\"><b></b><b></b></div><div class=\"cell fill\"><b></b><b></b></div><div class=\"cell fill\"><b></b><b></b></div><div class=\"cell \"></div><div class=\"cell \"></div></div><div class=\"tagrow\"><span class=\"tg\" style=\"border-color:#171915;color:#171915;background:#fff\">BUILDING INTERNAL RECORDS</span></div></div></div>", "s": "A memory error can interrupt setup, and cleanup then trusts unfinished data."}, {"t": "Bug 2: the old reference", "art": "<div class=\"win\"><div class=\"wbar\"><span>PHP / TEMPORARY MEMORY</span><span class=\"wdots\">\u25a2 \u25a2 \u25a2</span></div><div class=\"wbody\"><div class=\"wl\">WORKER REFERENCE</div><div class=\"refwrap\"><div class=\"note\">Old note<small>same space</small></div><svg class=\"arr\" viewBox=\"0 0 320 170\"><defs><marker id=\"ma\" markerWidth=\"10\" markerHeight=\"10\" refX=\"7\" refY=\"5\" orient=\"auto\"><polygon points=\"0,0 9,5 0,10\" fill=\"#171915\"/></marker></defs><path d=\"M16 36 C 150 54, 200 86, 266 120\" fill=\"none\" stroke=\"#171915\" stroke-width=\"3\" marker-end=\"url(#ma)\"/></svg><div class=\"box\">OLD</div></div><div class=\"tagrow\"><span class=\"tg\" style=\"border-color:#171915;color:#171915;background:#fff\">REQUEST ENDS</span><span class=\"emptybox\"></span></div></div></div>", "s": "Memory is released for reuse, but a worker can keep an old reference to it."}, {"t": "New data, old trust", "art": "<div class=\"win\"><div class=\"wbar\"><span>PHP / TEMPORARY MEMORY</span><span class=\"wdots\">\u25a2 \u25a2 \u25a2</span></div><div class=\"wbody\"><div class=\"wl\">WORKER REFERENCE</div><div class=\"refwrap\"><div class=\"note\">Old note<small>same space</small></div><svg class=\"arr\" viewBox=\"0 0 320 170\"><defs><marker id=\"mb\" markerWidth=\"10\" markerHeight=\"10\" refX=\"7\" refY=\"5\" orient=\"auto\"><polygon points=\"0,0 9,5 0,10\" fill=\"#171915\"/></marker></defs><path d=\"M16 36 C 150 54, 200 86, 266 120\" fill=\"none\" stroke=\"#171915\" stroke-width=\"3\" marker-end=\"url(#mb)\"/></svg><div class=\"box newbox\">NEW</div></div><div class=\"tagrow\"><span class=\"tg\" style=\"border-color:#D62828;color:#D62828;background:#fdecec\">STALE REFERENCE USED</span></div></div></div>", "s": "Later data fills that space. Under the right conditions, PHP can misuse it."}, {"t": "Before your login check", "art": "<div class=\"flow\"><div class=\"fwin\"><div class=\"fbar\" style=\"background:#171915;color:#fff\">INCOMING WEB REQUEST</div><div class=\"fbody\"><span class=\"tg\" style=\"border-color:#171915;color:#171915;background:#fff\">No account required</span></div></div><div class=\"conn\">\u25bc</div><div class=\"fwin\"><div class=\"fbar\" style=\"background:#D62828;color:#fff\">PHP REQUEST PROCESSING</div><div class=\"fbody\"><span class=\"tg\" style=\"border-color:#D62828;color:#D62828;background:#fdecec\">Vulnerable path</span></div></div><div class=\"conn x\">\u2715</div><div class=\"fwin\"><div class=\"fbar\" style=\"background:#5C5E57;color:#fff\">YOUR APP / LOGIN CHECK</div><div class=\"fbody\"><span class=muted>May not run first</span></div></div></div>", "s": "Some paths run before your app starts, so no password is needed for them."}, {"t": "What the patch changes", "art": "<div class=\"win\"><div class=\"wbar\"><span>PHP / PATCH APPLIED</span><span class=\"wdots\">\u25a2 \u25a2 \u25a2</span></div><div class=\"wbody\"><div class=\"patch\"><b>01</b> Safer failure handling<br><small>Stop without trusting half-finished work.</small></div><div class=\"patch\"><b>02</b> Clear stale state<br><small>Old references no longer survive a request.</small></div></div></div>", "s": "Safer failure handling and cleared stale state break these paths. Not every memory error becomes a takeover."}, {"t": "Update the running engine", "art": "<div class=\"win\"><div class=\"wbar\"><span>FIRST FIXED RELEASES</span><span class=\"wdots\">\u25a2 \u25a2 \u25a2</span></div><div class=\"wbody\"><div class=\"frow\"><span>PHP 8.2</span><b>8.2.35</b></div><div class=\"frow\"><span>PHP 8.3</span><b>8.3.36</b></div><div class=\"frow\"><span>PHP 8.4</span><b>8.4.27</b></div><div class=\"frow\"><span>PHP 8.5</span><b>8.5.12</b></div><div class=\"fnote\">Or a verified vendor backport.</div><div class=\"fnote dim\">WordPress: Tools &gt; Site Health &gt; Info &gt; Server &gt; PHP version</div></div></div>", "s": "Install the fixed PHP build and restart workers. Then check the live site."}];
var i=0,timer=null;
function render(){
  var s=STEPS[i];
  $('step').textContent=(i+1)+' / '+STEPS.length;
  $('title').textContent=s.t;
  $('say').textContent=s.s;
  var a=$('art'); a.innerHTML=s.art;
  a.style.animation='none'; void a.offsetWidth; a.style.animation='';
  $('prev').disabled=i===0; $('next').disabled=i===STEPS.length-1;
  [].forEach.call($('dots').children,function(d,n){d.classList.toggle('on',n===i);});
}
function go(n){i=Math.max(0,Math.min(STEPS.length-1,n));render();}
function stop(){if(timer){clearInterval(timer);timer=null;$('play').textContent='\u25B6 Play';}}
$('next').addEventListener('click',function(){stop();go(i+1);});
$('prev').addEventListener('click',function(){stop();go(i-1);});
$('play').addEventListener('click',function(){
  if(timer){stop();return;}
  if(i===STEPS.length-1)go(0);
  $('play').textContent='\u2759\u2759 Pause';
  timer=setInterval(function(){if(i===STEPS.length-1)stop();else go(i+1);},3600);
});
(function(){for(var n=0;n<STEPS.length;n++)$('dots').appendChild(document.createElement('i'));render();})();

Here’s what that means. A server uses memory as a temporary workspace, like a row of lockers. It puts a visitor’s data in a locker and keeps a note saying “use locker 2”. Reusing a locker later is normal. Keeping an old note and trusting whatever is in the locker now is the danger.

  • Bug one (CVE-2026-109618): running out of memory interrupted PHP while it was still setting up. Its clean-up then trusted half-finished work. The kitchen marked an order “ready” too early, got interrupted, and the next worker believed the label.
  • Bug two (CVE-2026-109619): a note pointing at a locker survived after the locker was emptied and handed to someone else. A later visitor’s request could then be read through that stale note. This is called a use-after-free, where “free” means handed back for reuse, not free of charge.

So a crafted request triggers an error at just the wrong moment, and PHP is left trusting data that was half-built or already freed. Under the right conditions that can leak private data left in memory, like passwords and keys, or let an attacker’s code run. Neither happens automatically, which is why these are rated on how bad they could be, not how easily they fire. The memory-disclosure side is the one that worries me most. Even without full control, an attacker might read a secret another request left behind a moment earlier.

A meeting room whiteboard still covered in the last meeting's notes, with a yellow sticky note reading API KEY
Leftover memory is a whiteboard nobody wiped. The next visitor can read it.

The fixes change how PHP behaves at that messy moment. It now stops safely, or shuts the worker down, and it clears leftover data so it can’t carry over to the next request. One honest note: not every memory error becomes a takeover. A crash can be just a crash. Turning the mistake into real control needs more conditions to line up, which is why broad, point-and-click attacks have been hard. That is not a reason to relax, it’s a reason the patch exists.

Why your usual protections didn’t help

This is the part that surprised people. The things you’d expect to save you were real, but this bug could strike before they got their turn.

Four cards: your password, the memory limit, Nginx and PHP-FPM, and your firewall, each with why it did not fully stop the bug
Each layer helps, but the bug could strike before its turn.

In plain terms, your password didn’t fail. The software handling the request could fail before the password ever mattered. A firewall still helps while you arrange the update, as long as traffic really passes through it, but even Cloudflare tells people to patch PHP rather than lean on the firewall. And one last myth to kill: “we use Nginx and PHP-FPM, not Apache” makes some versions of the attack harder, but the researchers found another path, so it’s not a reason to skip the update.

Check your PHP version right now

This is the only part that matters for staying safe. A version is safe if it’s 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or any newer release in those lines. Type yours in and see:

<div class="chk">
  <label class="lab" for="ver">Type your PHP version</label>
  <div class="field">
    <input id="ver" type="text" inputmode="decimal" placeholder="e.g. 8.2.18" autocomplete="off">
  </div>
  <p class="quick">Quick pick:
    <button type="button" data-v="8.4.27">8.4.27</button>
    <button type="button" data-v="8.2.18">8.2.18</button>
    <button type="button" data-v="8.3.6-0ubuntu0.24.04.8">8.3.6 (Ubuntu)</button>
    <button type="button" data-v="8.1.30">8.1.30</button>
  </p>
  <div class="out" id="out" hidden>
    <div class="badge" id="badge"></div>
    <p class="verdict" id="verdict"></p>
    <p class="todo" id="todo"></p>
  </div>
  <p class="foot">The upstream fix is in 8.5.12, 8.4.27, 8.3.36 and 8.2.35, or any newer release in those lines. Vendor builds can carry the same fix while keeping an older number.</p>
</div>
body { display: block; padding: 16px; place-items: normal; }
.chk { width: min(100%, 560px); margin: 0 auto; color: #171915; font-size: 15px; }
.lab { display: block; font-weight: 700; margin-bottom: 8px; }
.field input { width: 100%; box-sizing: border-box; font: 500 20px "Space Mono", ui-monospace, monospace; padding: 14px 16px; border: 2px solid #171915; border-radius: 12px; background: #fff; }
.field input:focus-visible { outline: 3px solid #5B4AE8; outline-offset: 2px; }
.quick { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 12px 0 0; font-size: 13.5px; color: #5C5E57; }
.quick button { min-height: 36px; padding: 0 12px; border: 2px solid #D7D7D7; border-radius: 999px; background: #fff; font: 500 13.5px "Space Mono", monospace; cursor: pointer; color: #171915; }
.quick button:hover { border-color: #171915; }
.quick button:focus-visible { outline: 3px solid #5B4AE8; outline-offset: 2px; }
.out { margin-top: 16px; border: 2px solid #E4E4E4; border-radius: 14px; padding: 16px; background: #fff; }
.badge { display: inline-block; font-weight: 700; font-size: 13px; padding: 5px 12px; border-radius: 999px; margin-bottom: 10px; letter-spacing: .02em; }
.badge.safe { background: #FFD400; color: #171915; }
.badge.update { background: #171915; color: #fff; }
.badge.danger { background: #D62828; color: #fff; }
.badge.eol { background: #D62828; color: #fff; }
.verdict { margin: 0 0 8px; font-size: 17px; font-weight: 700; line-height: 1.35; }
.todo { margin: 0; font-size: 15px; color: #3F413B; line-height: 1.5; }
.foot { margin: 12px 0 0; font-size: 12.5px; color: #5C5E57; }
@media (max-width: 560px) { body { padding: 10px; } }
var $ = function (id) { return document.getElementById(id); };
// Lowest patched build per supported branch. shortcut: branches 8.2-8.5 only; older or newer branches can't be judged by number alone.
var FIX = { '8.2': 35, '8.3': 36, '8.4': 27, '8.5': 12 };
function check(raw) {
  var out = $('out'), badge = $('badge'), verdict = $('verdict'), todo = $('todo');
  var s = String(raw).trim();
  // Validate the whole string as a version, with an optional vendor or prerelease suffix.
  var m = s.match(/^(?:php[\s-]*)?v?(\d+)\.(\d+)(?:\.(\d+))?([ _.+~-][0-9A-Za-z.+~-]*)?$/i);
  if (!m) { out.hidden = true; return; }
  out.hidden = false;
  var major = +m[1], minor = +m[2], patch = m[3] === undefined ? -1 : +m[3], suffix = m[4] ? m[4].trim() : '';
  var branch = major + '.' + minor;
  var pre = /(rc|alpha|beta|dev|snapshot|nightly)/i.test(suffix);
  var note = pre ? ' This is a prerelease, not a patched stable release.'
    : (suffix ? ' This looks like a vendor build, so the number alone can’t confirm the fix. Check the vendor’s advisory.' : '');
  badge.className = 'badge';
  if (major < 8 || (major === 8 && minor < 2)) {
    badge.classList.add('eol'); badge.textContent = 'No upstream fix';
    verdict.textContent = 'PHP ' + branch + ' is past its upstream support.';
    todo.textContent = 'There is no official fix for this line. A vendor may have backported it, so check their advisory, but moving to PHP 8.3 or 8.4 is the real answer.' + note;
    return;
  }
  if (FIX[branch] === undefined) {
    badge.classList.add('update'); badge.textContent = 'Can’t verify';
    verdict.textContent = 'This checker only knows PHP 8.2 to 8.5.';
    todo.textContent = 'For the ' + branch + ' line, check PHP’s advisory (php.net) for the fixed version.' + note;
    return;
  }
  if (patch === -1) {
    badge.classList.add('update'); badge.textContent = 'Add the patch number';
    verdict.textContent = 'You gave the ' + branch + ' line, but not the exact release.';
    todo.textContent = 'Enter the full version, like ' + branch + '.' + FIX[branch] + '. "' + branch + '" alone can’t be checked.';
    return;
  }
  if (patch >= FIX[branch]) {
    badge.classList.add('safe'); badge.textContent = 'Meets the fix';
    verdict.textContent = 'PHP ' + branch + '.' + patch + ' meets the upstream fix version for these two CVEs.';
    todo.textContent = 'Make sure PHP was restarted after the update, or the old version may still be running.' + note;
  } else {
    badge.classList.add('danger'); badge.textContent = 'Below the fix';
    verdict.textContent = 'PHP ' + branch + '.' + patch + ' is below the fixed ' + branch + '.' + FIX[branch] + '.';
    todo.innerHTML = 'Update to <b>' + branch + '.' + FIX[branch] + '</b> or newer, then restart PHP. If your host backported the fix, your package may already be patched while keeping this number, so check its advisory to be sure.' + note;
  }
}
$('ver').addEventListener('input', function () { check(this.value); });
document.querySelectorAll('.quick button').forEach(function (b) {
  b.addEventListener('click', function () { $('ver').value = b.dataset.v; check(b.dataset.v); $('ver').focus(); });
});

Here are the fixed versions, and when each line stops getting official security fixes. A longer support window is not a measure of how safe the code is, just how long the fixes keep coming.

PHP line First safe version Security fixes end
8.2 8.2.35 31 December 2026
8.3 8.3.36 31 December 2027
8.4 8.4.27 31 December 2028
8.5 8.5.12 31 December 2029

PHP 8.1 and older no longer get normal fixes. The PHP team made rare one-off backports for 7.4, 8.0 and 8.1, but those don’t restart proper support. If your host gives you a patched build of an old line, ask for its advisory and full package number, and don’t judge it by the version number alone.

Where to find your version

Not sure what you’re on? Here’s where to look:

  • WordPress: sign in as an admin and open Tools → Site Health → Info → Server, then read the “PHP version” line. Write down the whole number. A general “recommended PHP version” message is not a check for this specific fix.
  • cPanel: open Software → MultiPHP Manager and check your domain.
  • Plesk: open Websites & Domains → your domain → PHP.
  • Managed hosting: look in the site’s environment or PHP settings, and ask support for the full version if you only see “8.3”.
  • From a terminal: run php -v. Note that the terminal may use a different PHP from your website, so treat it as a separate check.

Here’s that terminal check on a server that still needs updating. The version 8.4.21 is older than 8.4.27, so this one is at risk:

A terminal showing php -v reporting PHP 8.4.21, older than the patched 8.4.27, with session upload progress still on
This server is on 8.4.21, older than 8.4.27, so it still needs the update.

On a plain PHP site with no dashboard, a developer can drop a tiny, protected page in the same site, read the version, then delete it straight away:

<?php
header('Content-Type: text/plain');
header('Cache-Control: no-store');
echo phpversion();

Protect that page behind a login at the server level, and remove it the moment you’re done. Don’t leave a public phpinfo() page up, because it shows far more about your setup than you want strangers to see.

Most PHP sites can’t just update, and that’s the real problem

If a third of PHP sites run a version with no fix, that’s the worrying part of the chart below. PHP 7 and PHP 5 reached their end of life, so no official patch is coming for them.

<div class="ch">
  <p class="intro">Share of all PHP websites, by version line. The grey ones get no official fix.</p>
  <ul class="bars">
    <li class="hi" style="--v:65.3"><b>PHP 8 <span>supported, fixable</span></b><i></i><span>65.3%</span></li>
    <li style="--v:26.9"><b>PHP 7 <span>end of life</span></b><i></i><span>26.9%</span></li>
    <li style="--v:7.7"><b>PHP 5 <span>end of life</span></b><i></i><span>7.7%</span></li>
  </ul>
  <p class="src">Source: W3Techs, 22 October 2026. About a third of PHP sites run a line with no official fix.</p>
</div>
body { display: block; padding: 16px; place-items: normal; }
.ch { width: min(100%, 620px); margin: 0 auto; color: #171915; }
.intro { margin: 0 0 16px; font-size: 15px; color: #3F413B; }
.bars { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; }
.bars li { display: grid; grid-template-columns: 1fr 58px; align-items: center; gap: 4px 10px; }
.bars b { grid-column: 1 / -1; font-weight: 700; font-size: 15px; display: flex; align-items: baseline; gap: 8px; }
.bars b span { font-weight: 500; font-size: 12.5px; color: #5C5E57; }
.bars i { height: 30px; border-radius: 8px; background: #D7D7D7; width: calc(var(--v) * 1%); animation: grow .7s ease-out; }
.bars li.hi i { background: #FFD400; box-shadow: inset 0 0 0 2px #171915; }
.bars span { font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; }
@keyframes grow { from { width: 0; } }
.src { margin: 16px 0 0; font-size: 12.5px; color: #5C5E57; }
@media (max-width: 560px) { body { padding: 10px; } }
@media (prefers-reduced-motion: reduce) { .bars i { animation: none; } }

On these older lines, the answer isn’t a quick patch, it’s a move. WordPress itself recommends PHP 8.3 or newer. Moving from PHP 7 to PHP 8 can break an old plugin or theme, so test on a copy first. But staying on PHP 7 is the bigger risk now.

Update, then prove the new version is live

The fix depends on who controls your PHP. Most small sites don’t run their own server, so this is usually a setting or a support ticket, not a command.

  1. Back up first. Files and database. Test the change on a staging copy where you can, especially when moving between PHP lines.
  2. Install the fix. On cPanel or Plesk, pick the newest 8.x your host offers. On a server you manage, update through your package manager, for example sudo apt update && sudo apt upgrade on Ubuntu or Debian. Then check login, checkout, uploads and forms still work.
  3. Restart PHP. This step gets missed a lot. A new version on disk does nothing while the old workers keep running. Restart php-fpm and your web server, or reboot. On cPanel, switching the version does this for you.
  4. Check again through the live site, with the tool above or php -v, and keep a note of the version and the time.
  5. Look for signs of trouble. Odd file changes, new admin accounts or strange activity deserve a closer look. A patch closes the door, it doesn’t remove a burglar already inside. Amazon suggests checking back to at least 17 October for this one.

A quick note on Ubuntu and Debian. They keep the old version number but add the fix underneath, so 8.3.6 on Ubuntu can already be safe. Check with apt changelog php8.3 or your host, rather than trusting the number alone. And if you run an agency, don’t forget the quiet sites: old campaign pages, staging domains and abandoned subdomains can each run a different, older PHP.

If you really can’t update yet

Sometimes you’re stuck waiting on a host or an old plugin. These settings reduce the risk, but they are a stop-gap, not a cure. The PHP team is clear that “only an update is a complete solution“. Your host needs to apply them in config that loads before requests are processed, then restart the workers:

; Blocks the main demonstrated attack path. Not a full fix.
session.upload_progress.enabled = 0

; PHP 8.5 only: blocks one more known path.
fatal_error_backtraces = 0

Three honest warnings. First, these must go in the main php.ini or the host’s config, not a per-site .user.ini, and not ini_set() or wp-config.php. By the time those load, the risky work can already be done. Second, on shared hosting you often can’t change these at all, which means your host is the only one who can protect you. Third, you may see advice online to set memory_limit = -1. The PHP team does list it, but it removes PHP’s own memory ceiling and needs the host to add outside limits instead. Don’t paste that into a live site on your own.

Quick answers

  • My site looks normal, so I’m safe? No. A normal-looking site doesn’t prove the PHP behind it is patched, or that nothing got in. Check the running version, and review activity separately.
  • Can I test my live shop with a public exploit? No. It can crash or damage a live site, and a random “checker” tool can be malicious itself. Verify by version and vendor instead.
  • A patched old version, kept forever? No. A backport can fix these two bugs without fixing everything else on an unsupported line. Plan the upgrade.
  • Is changing my WordPress password useful? It helps if a login might be exposed, but it doesn’t repair a PHP memory bug. If you suspect a break-in, rotate secrets from a clean device as part of cleaning up.
  • What exactly should I ask my host? “Does the PHP serving my domain include the fixes for CVE-2026-109618 and CVE-2026-109619, what’s the exact version, and have the workers been restarted?” Ask about staging too.

Know which PHP your site really runs, apply the real fix, and check it after. If you’d like us to do that for you, and make sure nothing breaks, our site updates service covers exactly this.

Share:

Comments

No comments yet. Questions, fixes and better ways are all welcome.

Leave a comment

Your email is never shown. Comments are checked before they appear, so yours may take a little while.

Start a project

Tell us what is
not working.

A few lines is enough. We read every message and reply personally by email. Or choose the way that suits you.