October 25, 2026
Black October 2026: What Happened, How the Exploits Work, and How to Defend
What βBlack Octoberβ is
By Vaibhav Kumar
14 min read
What "Black October" is
"Black October" is the community nickname for a pile-up of critical security disclosures between Oct 19 and 22, 2026, led by two unauthenticated remote-code-execution bugs in PHP itself. Posts on X counted more than 27 major incidents in about 48 hours. That count is unverified, but the pile-up was real.
This article covers what each incident is, the background behind it, how the exploits work, and how to fix and prevent them. If you only have five minutes, do these first:
- PHP: upgrade to 8.2.35, 8.3.36, 8.4.27 or 8.5.12, and set
session.upload_progress.enabled=0until the next release. - GitLab (self-managed): upgrade to a fixed release: 19.5.1, 19.4.3, 19.3.6, or the fixed version for your branch.
- Node.js, nginx, Next.js: patch Node.js (22.23.4, 24.22.1 or 26.12.1), nginx (1.31.7 or 1.30.6) and Next.js (16.4.1 or 15.5.28).
- Railway and LiteLLM: rotate every secret stored on Railway. If you ran LiteLLM 1.107.0, rebuild the environment and rotate everything it could reach.
- eMudhra: distrust the eight compromised intermediate certificates in any trust store that does not update itself.
Timeline: Oct 10 to Oct 22
The visible 48-hour spike on Oct 21 sat on top of two weeks of quieter setup and early exploitation. Newest first:
- Oct 22: eMudhra announces it is leaving the global TLS certificate business. Railway invalidates all API tokens. LiteLLM publishes its attack chain.
- Oct 21: PHP ships fixes for PHP2Shell. Node.js fixes 10 CVEs. GitLab fixes a second CVSS 10.0 RCE. LiteLLM 1.107.0 is published with a credential stealer. Railway discloses exposed environment variables. 32 UK government services are offline as a precaution.
- Oct 20: PHP2Shell details leak before the patch. Mozilla and Chrome confirm the eMudhra key compromise.
- Oct 19: Reuters reports a single hacker using AI agents to breach 100+ companies. OVHcloud CLI release assets are swapped for about 94 minutes. Cloudflare learns of the eMudhra keys.
- Oct 17: A researcher reports the weak eMudhra keys to Mozilla. AWS sees the first PHP2Shell exploitation attempt, four days before the patch.
- Oct 14: F5 publishes an 11-issue NGINX advisory with three critical bugs. Next.js and React ship an out-of-band security update.
- Oct 12: GitLab ships an emergency release for CVE-2026-97229, a CVSS 10.0 RCE.
- Oct 10: Attackers run a credential-stealing workflow inside LiteLLM's GitHub organization, eleven days before the public attack.
PHP2Shell: CVE-2026-109618 and CVE-2026-109619
PHP2Shell is a pair of CVSS 9.2 flaws in the PHP engine that let an unauthenticated attacker run commands on a web server, and updating PHP itself is the only complete fix. Because PHP powers a large share of the web, this was the headline event of the week.
Background
PHP's security team announced an upcoming release on Oct 20, and details leaked online the same day, before the patch existed. Fixes shipped on Oct 21 in PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12. Per reporting, Wiz Research found the bugs with AI-assisted tooling, and OpenAI said one was found with GPT-5.6 Sol and briefed the UK's NCSC ahead of disclosure.
Exploitation started before the fix. Amazon Threat Intelligence saw an attempt on Oct 17, four days before the patch, and watchTowr reported exploitation in the wild from Oct 21. Cloudflare's CEO said attempts rose from thousands in the hours before the announcement to millions afterward.
How it works
Both bugs live in how PHP handles a single HTTP request before your application code runs. That is why logins, input filtering and framework protections do nothing: the damage happens during request parsing.
- CVE-2026-109618, interrupted setup. PHP builds request data such as form fields in hash tables, and it bumps an internal counter before the new entry is fully allocated. If the allocation hits
memory_limit, PHP aborts the request, but the counter already counts a half-built entry. During cleanup, PHP treats that leftover memory as valid values, which an attacker can shape into command execution. - CVE-2026-109619, stale state. A PHP worker serves many requests in a row. If request memory is freed but a worker-wide pointer is not cleared, the next request on that worker can use the dangling pointer. Researchers confirmed paths through fatal-error backtraces (PHP 8.5, mod_php) and upload processing (mod_php and PHP-FPM).
Who is exposed
Any PHP runtime that serves HTTP is in scope: mod_php, PHP-FPM, php-cgi, FrankenPHP and LiteSpeed. PHP rates mod_php highest because the attack can land before any application code runs. Every PHP version back to 5.4 is affected by at least one of the two flaws. PHP 7.3 and older, including all of PHP 5, have no fix, and 7.4, 8.0 and 8.1 only have vendor packages or PHP's minimal source backports.
The fix is in PHP itself, so updating WordPress, Drupal, Joomla or your plugins does not help. Containers and appliances that bundle their own PHP also need inventorying.
How to fix it
Fixed releases by branch:
- 8.5 β 8.5.12
- 8.4 β 8.4.27
- 8.3 β 8.3.36
- 8.2 β 8.2.35 (reaches end of life on Dec 31, 2026)
- 8.1 and older β your distribution or LTS vendor's package, PHP's source backport, or an upgrade to a supported branch
- Inventory every host and container that runs PHP, internet-facing first.
- Upgrade to the fixed release or your vendor's backport, then restart all PHP workers so the fix takes effect.
- Until you are patched, reduce exposure. Set
memory_limit=-1and lock it withphp_admin_valueso applications cannot override it, rely on container memory limits instead ofRLIMIT_AS, and cap request size, field count and multipart parts at the proxy. - Set
session.upload_progress.enabled=0in php.ini or the FPM pool, not withini_set()or.user.ini, then restart workers. On PHP 8.5 also setfatal_error_backtraces=0. This also covers CVE-2026-115560, a further possible RCE path the PHP Foundation flagged on Oct 23. - If you cannot patch, take the site offline or restrict public access. The NCSC advised this, and it is why 32 UK government services went dark while teams patched. The UK minister said it was precautionary and that no compromise had been confirmed.
How to tell if you were hit
No single signal is conclusive, so look for several together:
- A web worker spawning a shell such as
shorbash. This is the strongest evidence. - Fatal "Allowed memory size exhausted" errors logged at "Unknown on line 0", and segmentation faults in the Apache log.
- Odd form submissions: very large fields, many multipart parts, deeply nested field names, or
PHPSESSIDandPHP_SESSION_UPLOAD_PROGRESSfields in an app that does not use them. - A
Set-Cookie: PHPSESSIDvalue that is unusually long or encoded.
GitLab: CVE-2026-110264, a second CVSS 10.0 in nine days
CVE-2026-110264 lets an unauthenticated attacker run code on a self-managed GitLab server, and patching an earlier Oct 12 fix does not cover it. It has the maximum CVSS 3.1 score of 10.0 and affects both Community and Enterprise Edition.
Background
GitLab published the fix on Oct 21 after a report through its HackerOne program from researcher joaxcar. The vulnerable code dates back to GitLab 7.0 in 2014, so every self-managed release from then until the fixed builds is affected. That does not mean it was exploited for twelve years. GitLab.com and GitLab Dedicated were patched centrally.
It followed CVE-2026-97229 by nine days, another CVSS 10.0 in the CI/CD regular expression compiler, fixed in an Oct 12 emergency release. The two are separate flaws, and Oct 12 builds remain vulnerable to the new one.
How it works
GitLab describes it as an integer overflow in its regular expression compiler. Reporting indicates the engine involved is Onigmo, Ruby's regex library bundled with GitLab. When the compiler processes a specially crafted pattern, size or count arithmetic overflows, which can lead to out-of-bounds memory access and, in the worst case, code execution.
How to fix it
Fixed releases by branch:
- 19.5 β 19.5.1
- 19.4 β 19.4.3
- 19.3 β 19.3.6
- 19.2 β 19.2.9
- 19.1 β 19.1.9
- 19.0 β 19.0.10
- 18.11 β 18.11.13
- 18.10 and older β no patch on your branch. Upgrade through GitLab's required upgrade stops to a fixed build.
- Check your exact installed version, even if you patched on Oct 12.
- Upgrade to the fixed release for your branch. There is no workaround, so upgrading is the only reliable remediation.
- Plan for downtime. 19.3.6 and 19.4.3 include database migrations, and single-node installs are down while they run. Multi-node installs can use GitLab's zero-downtime process.
- After upgrading, review activity from before the patch. A patch does not prove the server was not targeted.
Node.js, nginx and Next.js: three more layers of the stack
Node.js: Brotli decoder RCE (CVE-2026-111497)
Node.js released fixes on Oct 21 for 10 CVEs across the 22.x, 24.x and 26.x lines, and the worst is an out-of-bounds write in the Brotli decoder it bundles.
- Background. The flaw is in Brotli code that ships inside Node, not in your own program. The upstream fix was first committed in February 2026 but was only distributed as a Node security fix in October. OpenAI Codex Security researchers Khai Tran and Sylvie Mayer reported it.
- How it works. Malformed compressed input makes the decoder write past the end of an internal buffer, which can corrupt memory and in the worst case run attacker code. It triggers whenever the process decompresses Brotli data an attacker controls. That includes the
node:zlibmodule and anyfetch()call whose response arrives Brotli-compressed, so you do not have to call Brotli directly. - Fix. Upgrade to 22.23.4, 24.22.1 or 26.12.1. Node.js 20 reached end of life on Apr 30, 2026 and gets no fix, so it needs a version upgrade.
nginx: three critical memory bugs that depend on your config
F5's Oct 14 advisory covered 11 issues across NGINX products, with three rated CVSS 9.2 (v4.0), and the key point is that each one only matters if specific directives are in use.
- CVE-2026-15176 is a heap buffer overflow in the HTTP core, tied to the
aliasandrewritedirectives. - CVE-2026-93479 is a heap overflow in the
mapdirective's regular-expression handling, when a string expression references unnamed captures before the map's output variable. It affects 1.31.2 to 1.31.6 and 1.30.0 to 1.30.5. - CVE-2026-97205 is a use-after-free in the core module when asynchronous file I/O (AIO) and Server Side Includes (SSI) are both used.
- Two more fixes cover an HTTP/3 parsing bug that can add bytes when forwarding to a backend, and TLS session ticket key rotation.
To fix it, upgrade to nginx 1.31.7 (mainline) or 1.30.6 (stable). To triage first, run nginx -T to dump the full expanded configuration and check whether you use alias/rewrite, map with regex captures, or AIO together with SSI. Interim mitigations from F5: disable AIO or SSI for CVE-2026-97205, and use named captures, restricted to the block holding the regex match, for CVE-2026-93479. Ingress NGINX is largely unaffected because it removes the alias directive and does not enable SSI or HTTP/3 by default.
Next.js and React: image RCE, cache RCE, Server Actions DoS
On Oct 14 the Next.js team shipped an out-of-band update for three issues, two critical and one high.
use cacheRCE (GHSA-673q-mjm6-r5xr). An App Router app using Cache Components can be exploited without authentication when ause cachefunction returns multiple typed arrays. There is no known workaround, and the team says to upgrade even if you have no similar functions.- Image Optimization RCE (CVE-2026-96889). A memory bug in an upstream image library can lead to code execution when Next.js runs on glibc-based Linux and processes SVG. Interim workaround: make sure
sharpis 0.35.5 or later. - Server Actions DoS (GHSA-p3m5-59mp-jv5r). Crafted requests cause excessive CPU use and block other requests in the same process. Only App Router apps with at least one Server Action are affected.
Fix by upgrading to Next.js 16.4.1 (Active LTS) or 15.5.28 (Maintenance LTS).
eMudhra: eight certificate authority keys anyone could recover
On Oct 17 a researcher showed that the private keys of eight intermediate certificate authorities run by India's eMudhra could be recovered from their public certificates in under a second. It is a key-generation failure, not a data breach, and it matters because anyone holding such a key could forge a certificate your browser would trust for any website.
Background: how certificate trust works
When your browser loads a site over HTTPS, it checks that the site's certificate chains up to a root certificate authority it already trusts. Roots rarely sign sites directly. They sign intermediate CAs, and those intermediates sign the certificates websites actually present. So an intermediate's private key is a master key for every certificate beneath it: whoever holds it can mint a valid certificate for any hostname.
The response
- Mozilla and Google's Chrome Root Program confirmed the compromise on Oct 20. Mozilla removed trust in the root that signed the intermediates, and Chrome expects the removal on Oct 27.
- Cloudflare learned of it on Oct 19 and deployed mitigations and client updates.
- Software projects shipped denylists. Go fixed it in
crypto/x509as CVE-2026-111943, so chain verification fails when a chain contains one of the compromised intermediate keys, and Composer released ca-bundle updates on Oct 20. - On Oct 22, eMudhra announced it will leave the global SSL/TLS certificate business.
Supply chain and platform breaches: LiteLLM, OVHcloud CLI and Railway
Three incidents in the same fortnight had nothing to do with a bug in running code. They were attacks on release pipelines and stored secrets, and they share one target: credentials that unlock cloud and AI accounts.
LiteLLM 1.107.0: a stealer that runs when Python starts
On Oct 21 attackers published a malicious LiteLLM 1.107.0 to PyPI and Docker, and it stole cloud, developer and AI credentials on any machine where Python started. LiteLLM, an LLM proxy library with about 23 million weekly downloads, was live with the malicious release for roughly two hours. It is the project's second supply chain compromise this year.
- How they got in. Socket found malicious workflows executed inside LiteLLM's GitHub organization on Oct 10 and 12, eleven days earlier. They targeted secrets including a GitHub token and a GitHub App private key, and the attacker used them to obtain an organization owner's GitHub CLI OAuth token. On Oct 21, the attacker used owner privileges to bypass branch protections and point the release pipeline at a lookalike repository,
Berri-AI/litellm, one hyphen away from the realBerriAI/litellm. - How the payload runs. The wheel dropped
cook.pth,cook.pyandsitecustomize.pyinto the Python environment's library directory. Python executes.pthfiles at startup, so the code ran even if an app never imported LiteLLM. It collected credentials for AWS, Azure, Google Cloud and Kubernetes, SSH keys, browser data and environment variables, sent them to an attacker endpoint, and could fetch a second-stage backdoor. - What to do. If anything pulled 1.107.0, treat that environment as compromised: isolate it, rebuild from a clean image, and rotate every credential it could reach, starting with cloud, SSH, GitHub and AI provider keys. Starting Python or pip in an affected environment can re-trigger the malware, so do not poke around in place. Going forward, pin versions and check that a package's build provenance points at the legitimate repository.
OVHcloud CLI: the version number stayed, the bytes changed
On Oct 19 a compromised developer account replaced release assets for OVHcloud CLI versions 0.11.0 through 0.15.0, between 12:47 and 14:21 UTC (about 94 minutes). Checksums were replaced too. OVHcloud says source code and the build chain were unaffected, and 0.16.0 is the clean release. The advisory lists cloud credentials, SSH material, developer-tool data and AI-assistant stores among the targets.
The lesson is about trust anchors. A version-pinned download URL fixes a name, not the content behind it, and a checksum published beside the binary can be swapped by whoever can swap the binary. The incident was reported when a CI job's pinned SHA-256 started failing. A digest you approved earlier, or a verified release attestation, is the only check that holds.
To respond, do not run a suspect binary to see its version. Hash the preserved file and compare it with a digest you approved before, move to 0.16.0, and rotate any credentials that were on a machine that ran an affected build. Do the rotation from a trusted device.
Railway: customer environment variables exposed
Railway disclosed on Oct 21 that an unauthorized party may have accessed encrypted customer environment variables and, for some, obtained decrypted values. Railway says the impact is limited to a subset of users, but it tells all users to rotate secrets because the investigation is ongoing. It paused deployments, rotated bucket credentials, and invalidated every Railway API token on Oct 22.
To fix it, rotate every environment variable stored on Railway, including API keys, database passwords and private registry credentials. Rotate at the upstream provider first (AWS, GCP, GitHub, AI providers), then update the value in Railway. Create new Railway API tokens, and check provider dashboards and cloud bills for usage you do not recognize.
AI on both sides: why this looks like the start
AI showed up on both sides of Black October, finding bugs on one end and running intrusions on the other. What is proven is narrower than the social media posts suggest, but the direction is clear.
AI is finding the bugs
Wiz found the PHP flaws using AI-assisted tooling, and OpenAI said one of the two was found with its GPT-5.6 Sol model. Node's Brotli bug was reported by OpenAI Codex Security researchers. Wiz said demonstrating the PHP exploit took a chain of steps at the edge of what human researchers could practically do. The fix for the Node bug had also sat upstream since February, which shows how long a known-fixed bug can wait before it reaches a security release.
AI is also running the attacks
On Oct 19 Reuters reported, citing Norwegian firm NorForge, that one Chinese-speaking operator pointed AI agents at more than 1,200 e-commerce sites and broke into more than 100 companies, including Deere, SharkNinja and Cummins, across the US, Canada, Australia and Japan. The agents appeared to be Claude Code driving a DeepSeek model, coordinated through the open-source Cairn platform. NorForge saw the campaign because the operator's control panel was exposed with no login.
Why I expect more
This is my read, not an established fact. Finding a bug, writing an exploit and scaling it across thousands of targets are each getting cheaper, and attackers do not wait for patch windows. Defenders keep the same bottleneck they always had: a person reading an advisory and deciding what to do. Those two curves will not stay parallel. If the gap between disclosure and exploitation keeps shrinking, response time becomes the main security metric.
Defense playbook: patch faster, hold fewer secrets, automate the first response
The fix for Black October is not one patch. It is shrinking the time between a disclosure and a patched, rotated fleet, and shrinking what an attacker gets if you are late. The steps below are practical recommendations from this fortnight's incidents; the timing targets are my suggestions, not standards.
1. Know what you run
You cannot patch what you cannot find. PHP, Node and nginx hide in container images, appliances, CI runners and third-party products that bundle their own runtime. Keep a live inventory of runtime and version per host and image, and check the runtime that actually serves requests: the CLI install can differ from the web one.
2. Patch properly and verify
- Rebuild and redeploy container images, not just hosts, and re-check the running version afterward.
- Check distribution advisories. Vendors often backport fixes without changing the version number, and PHP 8.1 and older only get vendor or source backports.
- Keep end-of-life runtimes on a retirement schedule. PHP 8.2 loses security support on Dec 31, 2026, and Node.js 20 already has.
3. Cut exposure while you patch
When you cannot patch now, reduce what an attacker can reach. Put request-size, field-count and multipart limits at the reverse proxy, restrict public access, set the documented PHP ini switches, and take a system offline if it is both exposed and unpatchable. UK and European agencies did exactly that, and it is a legitimate response.
4. Hold fewer secrets and make rotation fast
Railway, LiteLLM and OVHcloud CLI all ended in stolen credentials. Use short-lived credentials where you can, scope tokens to one job, keep production keys out of developer machines, and keep private keys and wallet secrets out of environment variables. Write a rotation runbook per provider and rehearse it, because rotating forty keys under pressure goes badly the first time. Treat AI gateways and platform secret stores as high-value targets, since they hold many keys in one place.
5. Harden the supply chain
- Pin dependencies and images by digest, and verify build provenance or attestations rather than a checksum published next to the file.
- Use immutable releases, so a published version cannot be replaced.
- Protect release pipelines: branch protection that organization owners cannot casually bypass, least-privilege CI tokens, and alerts on workflow changes.
- Consider a short delay before adopting brand-new releases. LiteLLM's bad release was flagged quickly but still stayed live for about two hours.
6. Put defender agents on the repetitive work
If attackers run agents that never tire, the defense has to cover the same hours. Agents are well suited to the steps that are slow, repetitive and well defined. Keep people on the decisions.
- Inventory Agent. Maps every runtime and version across hosts and images, and flags drift. Human approval: not needed for read-only scans.
- Triage Agent. Matches each new CVE to what you run and your actual config (for example reading
nginx -T), then ranks by exposure. Human approval: review the ranking for critical items. - Patch Agent. Opens upgrade pull requests, runs tests, stages rollouts and verifies versions. Human approval: required before production changes.
- Secrets Agent. Rotates credentials the moment a vendor reports a breach, and checks provider logs for misuse. Human approval: required for production keys.
- Detection Agent. Watches for shell spawns, pipeline changes and odd logins, and drafts the incident timeline. Human approval: required before containment that causes downtime.
Give these agents guardrails: start read-only, grant the least privilege that works, log every action, and require approval for anything that changes production. Treat advisories and web pages they read as untrusted data, never as commands.