Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

PHP RCE 2026: CVE-2026-109618 and 109619 Explained

On 21 October 2026 the PHP project published two advisories within minutes of each other, both rated 9.2 on the CVSS v4.0 scale, both allowing remote code execution without any login. Within two days, government departments in several countries had pulled websites offline rather than risk running unpatched. For anyone in Nepal running a Laravel application, a WordPress site, or a custom PHP system on a VPS — whether built in-house or through web development work — this was not an abstract advisory. It was a same-week patching job on every server you own.

I run roughly twenty sites across two servers, so I spent the morning after disclosure doing exactly that work. This article is what I learned doing it: what the two flaws actually are, who found them, which servers are genuinely at risk and which are not, and the precise commands that fix it. If you would rather hand that work to somebody else, my Linux system administration service covers it.

What exactly are CVE-2026-109618 and CVE-2026-109619?

They are two separate bugs that share a root cause: PHP holds internal state that belongs to a single request, and in certain error paths that state outlives the memory it points at. Both were published by Jakub Zelenka on 21 October 2026, and both carry the same CVSS v4.0 vector — AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H — meaning network-reachable, no privileges, no user interaction, with high impact on confidentiality, integrity and availability.

The "high attack complexity" rating is the only comfort, and it is thinner than it sounds. It reflects that an attacker needs several attempts and some luck with memory layout, not that the attack needs special access.

CVE-2026-109618 — remote code execution via OOM corruption

PHP has historically handled out-of-memory conditions with longjmp(), a C mechanism that abandons the current call stack and jumps elsewhere. That works when the code it abandons was in a consistent state. The bug is that in some contexts, request memory is reachable before it has been fully initialised. If an allocation fails at that moment, PHP leaves the request structures half-built, and the shutdown routine then reads or branches on values that were never valid.

The advisory lists several ways an attacker can force that allocation failure using nothing but a crafted HTTP request:

  • Multipart requests without a usable declared content length can slip past the initial post_max_size check.
  • Each multipart part may carry an unlimited number of headers, and PHP's in-memory representation of a header costs far more than its bytes on the wire.
  • Empty file controls do not count towards max_file_uploads, yet each one still builds a file metadata array. Nested field names multiply that work.

The weakness classes recorded against it are CWE-416 (use after free), CWE-755, CWE-824, CWE-1325 and CWE-1419 — a list that reads like a summary of everything that can go wrong when cleanup code trusts a pointer it should not.

CVE-2026-109619 — remote code execution via stale global state

The second flaw is the more elegant of the two, and in practice the more dangerous, because it bridges two separate HTTP requests.

A PHP worker process serves many requests in its lifetime. Between them, global state — error state, session state, object state, extension state — must be reset before the memory backing it is released. The advisory frames the bug as a producer and a consumer:

  • A producer is any operation that leaves request-owned state published somewhere global. Fatal errors do it: a parse failure, an uncaught error, a failed require, a script hitting max_execution_time. So do out-of-memory conditions, and diagnostics raised during extension cleanup.
  • A consumer is any operation that runs before normal request initialisation and reads that state. Two are confirmed: diagnostics raised while applying certain INI settings, and session upload-progress handling.

Chain them and the attack is: send one request that fatally errors in the right place, then send a second request to the same worker that reads the freed memory before PHP has reset it. The second request is where code execution happens.

Two specifics are worth committing to memory. On PHP 8.5, fatal_error_backtraces stores request-owned backtrace state that is never reset on shutdown — this is the basis of the confirmed Apache mod_php exploit. And session upload progress is enabled by default and requires no session_start() call in your code, which means an application that never knowingly uses sessions is still exposed through it.

Who found these flaws, and who fixed them?

Both advisories credit the same four reporters: lcfr-hax, sagitzadik-wiz (Wiz), tomash-areion (Areion) and amyb-oai (OpenAI). The remediation was written by four PHP core developers: Arnaud Le Blanc, Ilija Tovilo, Jakub Zelenka and Niels Dossche.

That attribution matters, because a great deal of inaccurate commentary circulated in the first forty-eight hours. I received two separate forwarded messages about this before I checked the primary sources, and both contained errors — one stated flatly that no patch existed on a day when patches had already shipped, and both attached a severity score that did not match the published vector. One even credited the discovery to AI model names rather than to the researchers and vendors above.

The lesson generalises well beyond this incident: when a security claim reaches you through a forwarded message, read the vendor advisory before you act on it. For PHP that means php.net and the php/php-src GitHub security advisories, not a screenshot. I nearly dismissed a genuine critical advisory because the message describing it was wrong in its details.

Is my server actually vulnerable?

Not every PHP deployment is equally exposed, and the difference comes down to which SAPI you run.

Server setupCVE-2026-109618CVE-2026-109619
Apache mod_phpConfirmed. Full RCE demonstrated over HTTP with default limits.Affected. .htaccess and php_value settings apply before request startup, creating the early consumer.
PHP-FPM (nginx or Apache proxy)Source analysis suggests exposure; no complete exploit demonstrated.Conditional, but the session upload path works on a normal dynamic pool.
FrankenPHPLikely susceptible.No early consumer found in default config, which the advisory stresses is not immunity.
LSAPILikely, unvalidated.Conditional candidate.

If you run shared hosting in Nepal — and most small business sites here do — you are almost certainly on Apache with mod_php or LiteSpeed. That is the worst column of that table, and you cannot patch it yourself. Ask your host, in writing, which PHP build they are running and when they patched.

If you run your own VPS with PHP-FPM behind nginx, you are in better shape on the first bug and still exposed on the second through the upload-progress path.

A short history of PHP, and why this keeps happening

PHP began in 1994 as a set of CGI binaries Rasmus Lerdorf wrote to track visits to his online CV. He called them Personal Home Page Tools and released the source in 1995. PHP/FI 2.0 followed in 1997. The language as anyone would recognise it arrived in 1998, when Andi Gutmans and Zeev Suraski rewrote the parser for PHP 3 and renamed it to the recursive "PHP: Hypertext Preprocessor".

PHP 4 arrived in 2000 on the new Zend Engine. PHP 5 followed in 2004 with a real object model, and stayed in service for over a decade. PHP 6 was abandoned entirely — an ambitious attempt to make Unicode native to the language that collapsed under its own complexity, and the reason the version number jumps. PHP 7 landed in 2015 with the rewritten Zend Engine 3 and roughly doubled performance. PHP 8 followed in 2020 with the JIT compiler, named arguments, attributes and union types. PHP 8.5 shipped in November 2025.

Thirty years of continuous development, written in C, running on an enormous share of the web. Memory-safety bugs in that codebase are not a sign of neglect; they are the standing cost of the architecture. The notable ones form a recognisable pattern:

  • CVE-2012-1823 — the php-cgi query-string flaw that let an attacker pass command-line switches to the interpreter. Exploited at scale for years after the patch.
  • CVE-2019-11043 — a buffer underflow in PHP-FPM reachable through a common nginx configuration, giving remote code execution on a very large number of servers.
  • CVE-2024-4577 — CGI argument injection on Windows, caused by character-encoding conversion, and mass-exploited within days.
  • CVE-2026-109618 / 109619 — the current pair, and the first to target the error and cleanup paths that nearly every request touches.

What links them is that none required an application bug. Your code could be flawless and you would still be exposed, because the vulnerability is in the engine beneath it. That is the single most important thing for a site owner to understand: updating WordPress, or Laravel, or your own code, does nothing for this class of flaw. Only the PHP package itself matters.

How do I patch this properly?

The sequence below is what I ran on production servers running Ubuntu with the Sury PPA, which is the usual setup for a Nepali VPS.

Step 1 — find out what you are actually running

php -v
dpkg-query -W -f='${Version}\n' php8.3-fpm
apt-cache policy php8.3-fpm

Compare the installed version against the fixed list: 8.2.35, 8.3.36, 8.4.27, 8.5.12. Note that distributions backport fixes without changing the upstream version number, so Ubuntu's 8.5.4-0ubuntu1.5 may or may not contain the patch. Check the package changelog rather than guessing:

apt-get changelog php8.5-fpm | grep -i 109618

One caveat if you land on 8.2. It is patched, but the branch is on security-only support, and that ends on 31 December 2026 — about ten weeks after this advisory. 8.2.35 closes these two CVEs and then 8.2 stops receiving fixes at all. If you are choosing a branch to move onto rather than patching in place, choose 8.3 (security fixes until the end of 2027) or 8.4 (actively supported until the end of 2028). Patching to 8.2 today is correct; still being on it in 2027 is this same problem again, with the date already known.

Step 2 — apply the interim mitigations first

Do this before the upgrade, because it takes seconds and protects you if the upgrade turns out to need a maintenance window. Create a file that sorts last in your conf.d directory — ordering matters, and a later file will silently undo an earlier one:

; /etc/php/8.3/fpm/conf.d/99-zz-cve-2026-109618-109619.ini
session.upload_progress.enabled = 0
fatal_error_backtraces = 0

Then restart — not reload. The advisory is explicit that every worker must be replaced, and a reload can leave old workers alive:

sudo systemctl restart php8.3-fpm

fatal_error_backtraces only exists on PHP 8.5; it is harmless elsewhere. Upload progress is a progress-bar feature almost no modern framework uses, so disabling it is close to free.

Step 3 — upgrade the packages

sudo apt-get update
sudo apt-get install --only-upgrade $(apt-get -s upgrade | awk '/^Inst php8.3/ {print $2}')
sudo systemctl restart php8.3-fpm
php -v

That awk line exists for a practical reason. A naive apt-get install --only-upgrade php8.3-* fails on this stack, because transitional packages like php8.3-json have no installation candidate, and apt aborts the whole transaction rather than part of it. The upgrade appears to run and nothing changes. Taking the package list from apt's own upgrade plan avoids it.

Step 4 — verify, and do not trust the exit code

php -v
curl -s -o /dev/null -w "%{http_code}\n" https://yourdomain.com/

Check the version actually moved and your sites still answer. A package manager reporting success is not the same as a running service on the new build.

There is a subtlety worth knowing here. php -v reports the command-line binary, which is a different process from the one serving your website. FPM workers load their configuration at startup and keep it until they are replaced, so a server can genuinely be running patched code on the command line while every live request is still handled by a worker started before the upgrade. The only reliable confirmation is to check the running service rather than the CLI:

systemctl show php8.3-fpm -p ActiveEnterTimestamp
sudo systemctl restart php8.3-fpm

If that timestamp predates your upgrade, the workers serving traffic are still the old ones and the patch has not taken effect where it matters. This is the same reasoning behind restarting rather than reloading for the interim mitigation, and it is the step most commonly skipped.

What about PHP 8.1 and older?

There are no official releases for PHP 8.1, 8.0 or 7.4. The PHP security team published minimal source backports in branches named PHP-<version>-CVE-2026-109618-109619, intended for people building from source. They are not releases, and most site owners should not be compiling PHP.

If you are on 8.1 or older on a public server, you have three options, in order of preference: move the application to a supported branch, get a backported build from your distribution vendor, or take the service offline. Running an unsupported PHP branch on a public web server during an actively discussed RCE is not a position to be in.

Stuck on an old PHP version with no patch coming?

This is the position a lot of Nepali businesses are in this week: an application built on PHP 7.4 or 8.1 a few years ago, running fine, and suddenly on the wrong side of a critical advisory with no official fix available. There is no safe way to leave it there.

Moving an application onto a supported branch is usually smaller work than people expect. Of the two Laravel applications I migrated this week, both ran on PHP 8.3 unchanged — the work was testing each page and switching the handler, not rewriting code. Where a rewrite genuinely is needed, it is far better to learn that from a test run than during an incident.

This is work I do: website migration to move an application onto a supported branch, custom software development where the code itself has to change, and support and maintenance if you would rather someone else watched the advisories. Not sure which applies? Tell me what you are running and I will tell you honestly — including if the answer is that you do not need me.

On my own older server this was straightforward to resolve. Two Laravel 8 applications were still bound to PHP 8.1. Both ran cleanly on 8.3 once tested, so the handler socket moved from php8.1-fpm.sock to php8.3-fpm.sock, the sites were verified page by page, and the entire 8.1, 8.0 and 7.4 package set was then removed. Fifteen packages went, three supported branches remained, and every site stayed up.

One hard-won caution from that work: before removing a PHP branch, search your Apache configuration with grep -R, not grep -r. The lowercase form does not follow symbolic links, and sites-enabled is a directory of symlinks. It will tell you nothing uses the version you are about to delete, and it will be wrong. I took two sites down for several hours learning that.

Checklist for a Nepali business owner

  1. Find out who controls your PHP version. On shared hosting it is your provider. On a VPS it is you. There is no third option.
  2. Ask your host in writing which PHP version they run and the date they patched. A host that cannot answer within a day is telling you something.
  3. If you self-host, patch today and apply the two INI settings while you do it.
  4. Check for an unsupported branch. Anything on 8.1 or below needs a plan this week, not this quarter.
  5. Review your logs afterwards with error_reporting=E_ALL in a staging environment. The advisory specifically suggests looking for warnings raised before your script runs, diagnostics during cleanup, and requests that hit the execution timeout.

If any of that is beyond what you want to take on, support and maintenance is the service that covers it, and you can get in touch directly.

The part most coverage missed

Almost everything written about this incident in its first week focused on severity — the 9.2 score, the percentage of the web running PHP, the government sites taken offline. Very little of it addressed the question an actual site owner has, which is whether their specific setup is reachable and what single command fixes it.

The honest summary is narrower than the headlines. If you run Apache with mod_php, this is as serious as it has been described and you should treat it as an emergency. If you run PHP-FPM, the confirmed path is the session upload-progress one, which a single INI line closes while you schedule the upgrade properly. If you are on managed hosting, your exposure is entirely a function of how quickly your provider moves, and your job is to ask them.

And if you are still on PHP 8.1 because an old application was never migrated, this advisory is the moment that decision stops being deferrable. There is no patch coming.


Written by Kokil Thapa, full-stack developer, Kathmandu. Sources: the PHP project advisories GHSA-w8hw-w9fx-j79w and GHSA-6wmf-4rv5-p3rp, and php.net release announcements. Last reviewed: October 2026.

Frequently Asked Questions

PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12. Anything older on a web-facing server is affected.

No. The flaw is in the PHP engine itself, so only the PHP package matters.

Set session.upload_progress.enabled=0, add fatal_error_backtraces=0 on PHP 8.5, then restart every worker.

Both score 9.2 on CVSS v4.0, the second-highest band. An unauthenticated attacker reaching your server over normal HTTP can, in confirmed cases, execute code as your web server user. The "high attack complexity" rating means several attempts may be needed, not that special access is required.

Both advisories credit four reporters — lcfr-hax, sagitzadik-wiz of Wiz, tomash-areion of Areion, and amyb-oai of OpenAI. The fixes were developed by PHP core contributors Arnaud Le Blanc, Ilija Tovilo, Jakub Zelenka and Niels Dossche, and published on 21 October 2026.

Yes, though not immune. Full remote code execution was demonstrated against mod_php with default settings. For PHP-FPM the advisory describes exposure as conditional, but the session upload-progress path does work on a normal dynamic pool, so FPM servers still need patching.

Upload progress is enabled by default in PHP and requires no session_start() call in your code to be reachable. That is what makes it dangerous — an application that never knowingly touches sessions is still exposed through it, which is why disabling the setting is the first recommended step.

No official patched release exists for 8.1, 8.0 or 7.4. The PHP security team published minimal source backports for people who build from source. Realistically you should migrate the application to a supported branch, obtain a vendor backport, or take the service offline.

Distributions backport fixes without changing the upstream version number, so the version string alone proves nothing. Run apt-get changelog php8.3-fpm and search it for the CVE identifiers. If they do not appear, the fix has not reached your package yet.

Restart. The advisory requires every worker process to be replaced, and a reload can leave existing workers running the old configuration. Since the attack depends on a worker carrying stale state between requests, a surviving worker is exactly the problem you are trying to eliminate.

Ask two specific questions in writing: which exact PHP version your account runs, and on what date they applied the 21 October 2026 patches. Vague reassurance is not an answer. If they cannot tell you within a day, that itself is useful information about the service.

Only partially, and you should not rely on it. A WAF can limit request body size and block malformed multipart requests, which raises the difficulty of CVE-2026-109618. It does not address the stale-state path in CVE-2026-109619, where the malicious second request can look entirely ordinary.

Transitional packages such as php8.3-json have no installation candidate, and apt refuses the entire transaction rather than part of it. The command reports an error and exits, leaving versions untouched. Build the package list from apt's own upgrade plan instead of using a wildcard.

Yes. On 23 October 2026 the PHP project noted a further session-related issue, CVE-2026-115560, which will receive a full advisory alongside the next release. It requires a particular combination of configuration and runtime ini_set() calls. Keeping session.upload_progress.enabled=0 is the recommended interim step.

There is no simple indicator, which is why patching quickly matters. In an isolated environment, enable log_errors with error_reporting=E_ALL and review logs for warnings raised before your script executes, diagnostics during cleanup, and requests hitting the execution timeout. The advisory cautions that clean logs do not prove safety.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: