DevOps and reliabilityTechnical article

How a memory error in PHP can run a stranger's code on your server

On 21 October 2026 the PHP project told every PHP user to update at once. Two critical flaws can let a stranger run code on your web server, and on Apache a demonstrated attack fires before your own code has started. Here is what broke, who is exposed and what to do this week.

In short

  • On 21 October 2026 PHP fixed two critical flaws, CVE-2026-109618 and CVE-2026-109619 (CVSS 9.2 each), that can let an unauthenticated attacker run code on a web server. The risk is highest on Apache with mod_php.
  • Bug one leaves a request's data half built when PHP runs out of memory, and clean-up trusts it. Bug two leaves a pointer to freed memory in a worker that serves the next request.
  • PHP runs 69.3% of sites with a known language, and at least 34.6% of those run a version below 8 that the project no longer fixes.
  • Only an update is a complete fix. Find every copy of PHP, update or take your vendor's patch, restart the workers, and treat the stop-gap settings as a bridge.

Two critical bugs in the language under most of the web

On 21 October 2026 the PHP project published an advisory titled "Critical remote code execution vulnerabilities, update now". PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 fix two bugs, CVE-2026-109618 and CVE-2026-109619, that can allow unauthenticated remote code execution. Both score 9.2 out of 10 on the CVSS 4.0 scale. PHP says every installation serving web requests is at risk and rates mod_php highest, where "an unauthenticated request can trigger the attack before any application code runs".

That phrase is why a founder should care. Your login, your input checks and your framework are application code, and here none of them gets a say. The flaw sits in the PHP engine underneath.

W3Techs reports that PHP is used by 69.3% of all the websites whose server-side language it can identify. The figure shows 100 PHP sites split by version, as of 24 October 2026.

Figure 1How much of the web runs a PHP that no longer gets fixes
100 PHP sites34.6%of PHP sites runPHP 7 or older, which gets no fixesPHP 8.x65.4%PHP 7.x26.8%PHP 5.x or older7.8%W3Techs counts PHP at 69.3% of known sites

Swipe sideways to see the whole figure

Count
W3Techs, 24 October 2026. Each square is one percent, rounded. PHP 8.x includes 8.0 and 8.1, which the PHP project also no longer fixes, and the page does not split 8.x further, so the amber and red squares are a floor.

Switch to all sites. PHP 7 and older is 34.6% of PHP sites (26.8 plus 7.7 plus 0.1), and 34.6% of 69.3% is about 24% of all sites with a known language. The project no longer fixes 8.1 and older, and W3Techs does not split 8.x by minor version on that page, so 24% is a floor. The advisory does not say how far back the flaws reach, but the team prepared source fixes for 7.4, 8.0 and 8.1 "to help people building from source or without access to an LTS vendor", so I read those branches as affected.

Bug one: running out of memory at the wrong moment

CVE-2026-109618 is about what PHP does when it runs out of memory. A request passes through three stages: start up, where PHP builds the request's data, then your code, then shut down, which frees that data. When an allocation fails, PHP uses the C call longjmp() to leave the request at once. In some places the request's data can be reached before it is fully set up. The jump skips the rest of the setup, and shut down still runs and treats the half-built data as valid.

Figure 2A request that runs out of memory halfway through setup
1 Start upbuilds the request's data2 Your codelogin, checks, the work3 Shut downfrees that dataRequest data: six slotsslot 1freedslot 2freedslot 3freedslot 4freedslot 5freedslot 6freedShut down walks the slots and frees each one. All six are real, so it is safe.

Swipe sideways to see the whole figure

The request is
Simplified. Six slots stand in for the internal data PHP builds for a request; the real structures differ. The order follows the PHP advisory GHSA-w8hw-w9fx-j79w: an allocation fails during start up, PHP leaves with longjmp(), and shut down reads data that was never fully set up.

Switch to the second request. The allocation fails while slot 4 is being set up, slots 4 to 6 are never built, your code never runs, and shut down reads 3 of 6 slots that hold nothing valid. Corrupted memory is how a crash can turn into code execution. The CVSS vector marks attack complexity as high, which fits the advisory's warning that reliability depends on the PHP build, its extensions, the web server in front and how memory is laid out. On mod_php the advisory validated a complete unauthenticated attack over HTTP with default limits. On PHP-FPM it found the code susceptible but no complete exploit.

A stranger has to make PHP run out of memory without tripping your limits. The advisory lists ways. A multipart upload without a usable declared length passes the first post_max_size check and keeps supplying data. One part can carry any number of headers, and each is stored in memory.

Figure 3The limit counts bytes sent, the memory counts what they become
Bytes sent7.2 MiBlimit: post_max_size, 8 MiB8 MiBMemory PHP uses143 MiBlimit: memory_limit, 128 MiB128 MiBThe memory limit is hit and the byte limit never trips.250,000 lines x 30 bytes = 7.2 MiB sent. 250,000 lines x 600 bytes = 143 MiB in memory.

Swipe sideways to see the whole figure

The 30 bytes sent and 600 bytes of memory per header line are made up for the picture. The advisory says a part can carry any number of headers and that they can use far more memory than their size on the wire; it gives no ratio. 8 MiB is the post_max_size it suggests for an app with no uploads, and 128 MiB is an example memory_limit.

Drag the slider to 250,000 header lines. At a made-up 30 bytes each that is 7.2 MiB sent, under an 8 MiB limit. At a made-up 600 bytes of memory each it is 143 MiB, over a 128 MiB limit.

Bug two: a worker that remembers too much

CVE-2026-109619 is a different failure. A PHP worker is a long-lived process that serves request after request, and it keeps global state: error, session, object and extension data. Some of that state points into memory owned by one request. If the request ends abnormally, with a fatal error or a timeout, the step that clears the pointer can be skipped while the memory is freed. The pointer now dangles, and the next request on the same worker can use it in an early step, before normal setup replaces it.

Figure 4A worker that remembers what it should have forgotten
Request A's memoryfreeddanglingreset skippedOne PHP workera long-lived processglobal stateerror, session, object dataRequest B: early stepruns before normal setupreadsread after freeThe pointer leads to freed memory, which is read as if it were A's data.

Swipe sideways to see the whole figure

The worker is
Simplified; the names and shapes are not PHP's own. The order follows the PHP advisory GHSA-6wmf-4rv5-p3rp: state kept by a worker points to memory owned by one request, and a later request on the same worker can use it before normal setup. A fresh process per request is PHP's pm.max_requests=1, which it lists as a way to reduce exposure.

Run it both ways. The advisory says the request that leaves the pointer and the request that uses it are different HTTP requests on the same worker. Its strongest demonstrated trigger is PHP 8.5's fatal error backtraces. Session upload progress is another: it is on by default and needs no session_start(). The confirmed impact is unauthenticated use-after-free, memory disclosure and remote code execution with the web worker's privileges, validated on mod_php and, through the session path, on PHP-FPM. A fresh process for every request (pm.max_requests=1) removes the stale pointer; PHP lists it as a way to reduce exposure, at a cost in speed.

What a stranger could do with it, and where a startup is exposed

The advisories stop at remote code execution with the web worker's privileges. For a business that means code running as your web server's user. It can read the application's configuration, which usually holds database passwords and API keys, change anything the app can, leave a back door that survives the patch, and reach other machines the server can talk to. That is my reading of what code execution generally allows; the advisory describes none of it.

How likely is an attack now? In an alert updated on 23 October, the Canadian Centre for Cyber Security said open-source reporting indicates a proof of concept is available and that it had identified no active exploitation, adding that this "can change quickly".

Two published figures give the stakes, and neither is about PHP. Verizon's 2026 Data Breach Investigations Report says 31% of breaches now start with software vulnerabilities, ahead of stolen passwords as the top way in (incidents from 1 November 2024 to 31 October 2025). IBM and Ponemon put the 2026 global average cost of a data breach at $4.99 million, up 12% and a record, across all kinds of breach.

Where a startup's version of this hides:

  • A site an agency built years ago, still on PHP 7.4, on a server nobody signs in to.
  • A container image built last spring. Patching the host does nothing for the PHP inside it.
  • A shared host or managed platform where you never see the PHP version. Ask for it in writing.
  • A vendor product that bundles PHP. The Cyber Centre tells teams to inventory third-party products and containers that include it.
  • A server whose php -v looks old. Distributions can backport fixes without changing the version, so check the vendor's notice as well as the number.

What the stop-gaps close and what they leave open

For people who cannot update at once, PHP published settings, but updating is the only complete fix. The figure lines them up against five ways in.

Figure 5Which doors each stop-gap closes
PHP's own memory limit is hitCVE-2026-109618 · memory_limit = -1closedSession upload progressCVE-2026-109619 · session.upload_progress.enabled = 0closedFatal error backtraces (PHP 8.5)CVE-2026-109619 · fatal_error_backtraces = 0closedThe machine really runs out of memoryCVE-2026-109618 · low post_max_size and input limitsnarrowedEarly warnings, other fatal errors, timeoutsCVE-2026-109619 · no complete setting existsopen3 closed, 1 narrowed, 1 openSettings reduce exposure to named paths. They do not make an installation safe.

Swipe sideways to see the whole figure

The server has
From PHP's advisory of 21 October 2026. Closed means the confirmed path is blocked, narrowed means harder to reach, open means no setting covers it. The advisory says each setting blocks only what it names and that updating is the only complete fix.

Switch to the stop-gap settings. Three doors close, one narrows and one stays open. memory_limit=-1 removes the memory-limit failure, but a real allocation failure can still reach the bug, and a low post_max_size with input limits only makes that harder. session.upload_progress.enabled=0 and fatal_error_backtraces=0 each block only the confirmed path they name. Early warnings, other fatal errors and timeouts have no setting: PHP says there is no complete generic configuration mitigation.

What to do this week

Find every copy of PHP

List your servers, containers and vendor products, with the PHP version and how each serves the web.

php -v                              # version on this machine
apachectl -M | grep -i php          # a php module here means mod_php
docker run --rm your-image php -v   # the version baked into an image

Update, then restart

Move to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or your distribution's updated package, then restart PHP-FPM or Apache so running workers load it. On 8.1 or older, ask your vendor for a backport and plan the upgrade. The project's source backports for 7.4, 8.0 and 8.1 are for teams that build from source.

If you cannot update today

; PHP's partial measures of 21 October 2026. Restart every worker afterwards.
php_admin_value[memory_limit] = -1   ; in the FPM pool, so apps cannot change it
post_max_size = 8M                   ; 32M if you need uploads
session.upload_progress.enabled = 0
fatal_error_backtraces = 0           ; PHP 8.5 only

With the memory limit gone, put a container or cgroup memory limit around the workers so a runaway request cannot take the machine down, as the advisory suggests. Avoid RLIMIT_AS and RLIMIT_DATA, which can cause the very allocation failure the bug needs.

Watch for signs, and expect a third report

Read web server and application logs for unexpected processes, files, accounts and outbound connections, as the Cyber Centre advises. On 23 October PHP added CVE-2026-115560, an unlikely path that needs an application to switch session.serialize_handler to php at runtime with ini_set() while upload progress is on. Its full advisory is due with the next release; session.upload_progress.enabled=0 covers it.

How a patch moves through a real product

Wanguza, the school admissions CRM, runs on Next.js on Vercel, so these PHP bugs are not in it. Its release path is the useful part, because an emergency update is only calm when the normal release already is. Every pull request gets its own preview deployment, which works as a staging copy. Checks run there and the change is tried with test schools and test messages. Merging to main deploys to production, and if something goes wrong the previous deployment comes back in one step. See the case study.

Questions to answer today

  1. Which PHP versions do we run, where, and who owns each one?
  2. Does Apache serve any of it through mod_php?
  3. Is anything on 8.1 or older, and who provides its fixes?
  4. Which of 8.5.12, 8.4.27, 8.3.36, 8.2.35 or a vendor package is running now, and were the workers restarted?
  5. If we have not patched, which stop-gap settings are on, and who confirmed it?
  6. How many hours pass between a critical advisory and the fix reaching production, and how do we undo a bad release?
  7. Who reads security advisories for us?

Sources

  1. PHP, Security advisory: Critical remote code execution vulnerabilities, update now (21 October 2026)
  2. PHP, Informational advisory: Session upload progress, CVE-2026-115560 (23 October 2026)
  3. PHP GitHub advisory GHSA-w8hw-w9fx-j79w: RCE via OOM corruption, CVE-2026-109618
  4. PHP GitHub advisory GHSA-6wmf-4rv5-p3rp: RCE via stale global state, CVE-2026-109619
  5. Canadian Centre for Cyber Security, AL26-026: Critical vulnerabilities impacting PHP (21 October 2026, updated 23 October)
  6. W3Techs, Usage statistics of PHP (read 24 October 2026)
  7. Verizon, 2026 Data Breach Investigations Report
  8. IBM and Ponemon Institute, Cost of a Data Breach Report 2026

The animated figures are simplified illustrations that use the numbers stated in the text. Where a figure is modelled on a real incident, the sources above are the full accounts.

The service behind this articleDevOps Architecture & Platform ReliabilityRelated case studyWanguza: a WhatsApp-first admissions CRM for African schools
Aslam Sarfraz profile photo

Aslam Sarfraz

AI, DevOps and full-stack engineer

More articles

AI systems · 10 min read

Why your AI agent works in the demo and fails in production

A strong agent that solved about 61% of retail tasks on one try solved under a quarter of them on all eight tries. Five reasons a demo hides this, each with arithmetic you can check.

AI systems · 10 min read

How AI backends handle millions of requests without falling over

A chat answer keeps a GPU, a block of memory and a connection busy for many seconds. What that does to a backend, how serving systems cope, and what a startup should copy.

Full-stack engineering · 8 min read

Why startup backends break at the first big traffic spike

Ticketmaster took 3.5 billion requests in one sale and Segment merged more than 140 services into one. The failures behind spikes are rarely exotic, and each one has arithmetic you can do before launch.

DevOps and reliability · 8 min read

How one config change takes down production, and how to contain yours

CrowdStrike, Google Cloud, AWS and Cloudflare each had a major outage between July 2024 and November 2025, and none began with an attack. What happened, what it teaches and what to put in place.

LinkedIn outreach · 7 min read

Designing a LinkedIn outreach pipeline: a fixed capacity and an unseen limit

Outreach is a pipeline with a fixed capacity, a limit nobody publishes and very small samples. The planning numbers I use and the engineering behind them.