Blast Radius

All three fixes land under the application

PHP, Brotli and GitLab each let an unauthenticated request reach a parser that application code does not own, and all three reached the known exploited catalog with a remediation deadline of three days or less.

Rows of black server racks along a white floor in a data centre aisle, viewed from a low angle with blue structural columns overhead.
Lead image Server racks in a data centre aisle. The PHP2Shell flaws reach any host that runs PHP behind a web server, so the fix is a fleet operation rather than a single service restart. The photograph shows the Wikimedia Foundation data centre and is not a record of an affected site. · Photo: Helpameout / Wikimedia Commons · CC BY-SA 3.0
On this page

The three stories this week share a shape that matters more than any single score. In each one the vulnerable code sits below the application. The PHP request lifecycle, the Brotli decoder a runtime calls on an inbound body, and the regular expression compiler GitLab reaches from its CI lint endpoint all sit under code the application author wrote. Application code never sees the malformed input, so no application patch closes any of them.

All three reached the CISA known exploited catalog inside four days. PHP was listed on 26 October with a remediation deadline of 29 October, Brotli on 27 October with a deadline of 10 November, and GitLab on 29 October with a deadline of 1 November. Two of those windows are three days, the shortest notice the catalog issues.

Install base puts PHP first, Brotli second and GitLab third, and that ordering holds against what an attacker must hold before the first request. PHP asks for nothing beyond a reachable endpoint, while GitLab needs a credential that can push to a public project. Brotli sits between the two, because its precondition is an application that will decompress a body the attacker controls. Most HTTP frameworks do that by default.

PHP frees request memory and then reads it

What happened: PHP uses longjmp() to abandon the current context when an allocation fails. Not every caller accounts for the jump. Request memory can therefore be left uninitialized before the next allocation, and the cleanup path then reads or branches on it. The second flaw in the pair, CVE-2026-109619, is the same shape one step later. Global state that points into request memory is not always reset, or is repopulated after the reset, so a later request reads a pointer into freed memory. The CVE record states that multiple out-of-memory strategies trigger both consistently, and that the result is remote code execution.

The pair was fixed on 21 October in 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Source backports for 7.4, 8.0 and 8.1 went out for anyone building without an LTS vendor. Google’s CNA rates both 9.2 under CVSS 4.0, while NVD rates 109618 at 9.8 under CVSS 3.1. That gap follows from how each model scores a use-after-free that depends on a failed allocation.

Exploitation is not theoretical. Arctic Wolf Labs recorded scanning and active attempts shortly after disclosure. One request ran id, printed a confirmation marker, changed to /tmp and fetched a second stage from 92.118.112.100. The Delft Threat Intelligence Lab counted 85,521 exploitation requests from 798 source addresses across 74 countries between 21 October and 4 November. Those addresses sat in 336 autonomous systems. Amazon’s MadPot honeypots saw an attempt on 17 October, four days before the patch existed.

What it reaches: Any host that runs PHP behind a web server, which the advisory lists as PHP-FPM, mod_php, php-cgi, FrankenPHP and LiteSpeed. The PHP security team calls Apache mod_php the highest-risk configuration. The researchers demonstrated code execution from a single unauthenticated request before application code runs. Shared hosting and container images pinned to a PHP minor line move slowest. They are also where one compromised worker exposes neighbouring tenants.

What to check:

  • The PHP version from php -v on each host and inside each built image, rather than the version in a composer platform constraint.
  • Whether Apache loads mod_php instead of routing to PHP-FPM, since that is the configuration the researchers used.
  • Container tags that pin a minor line, such as php:8.3-apache, and whether those images were rebuilt after 21 October.
  • Whether the PHP Brotli extension is installed, because it carries the decoder flaw described next.

What would change the assessment: A credible report that the out-of-memory precondition cannot be reached through a request body under a default php.ini would move both CVEs down sharply. Every observed attempt so far depends on controlling allocation size. The 17 October honeypot hit is the fact that argues hardest against that reading.

The Brotli decoder writes past an eight byte buffer

What happened: A Brotli stream is a sequence of metablocks, and inside one the data is split into runs called blocks. The blocks come in three independent kinds, each with its own table and its own counter. When a counter reaches zero the decoder reads a block switch. A switch that declares the type already in use is invalid, and so is a switch that runs out of input partway through. The block-switch helper returned a plain boolean, so both faults produced the same false value, and the caller read that as a request for more input. Between calls the decoder holds unconsumed bytes in an eight-byte internal buffer, and an invalid switch never drains it. Writes past the eighth byte therefore land outside the buffer, and the displacement is a signed 32-bit value. That leaves an attacker a destination within roughly 2 GB and 16 bytes of their own content to place there.

The bug shipped in Brotli 1.0.3 in March 2018. The fix, commit 0ed4268a, was written on 27 February 2026 and stayed in the public master branch for eight months with no release and no advisory. That is why the same trigger arrived in several ecosystems at once. It shipped in Brotli 1.0.10, 1.1.1 and 1.2.1. Node.js 22.23.4, 24.22.1 and 26.12.1 followed on 21 October, along with Bun 1.4.4, PyPI brotli 1.2.1 and brotlicffi 1.2.1.0.

What it reaches: ELLIO reproduced code execution on x86-64 against four targets running the official Node.js 24.22.0 build. They were a plain node:http server calling node:zlib, the node:24.22.0-slim image running as root, Express 5.3.0 with express.json(), and Koa 3 with @koa/bodyparser 6. Imperva’s Red Team had shown the same on arm64 a day after the fix. On 26 October ELLIO’s sensors recorded a 23,069,983-byte Brotli body from 94.154.43.7 built against that exact binary. Its ROP chain reused no gadgets from either public exploit. The chain did not reach execvp in ELLIO’s testing, making it a working framework with a defective final stage rather than a confirmed compromise.

The denial of service needs no exploit chain at all. Most of four hundred Express attempts in ELLIO’s lab crashed the worker instead of returning a shell. Where core dumps are enabled, each crash writes a core that can hold another user’s session material. Several other decoders do not crash. The same bytes against Python Brotli 1.2.0, brotlicffi, the PHP extension and the Ruby gem return zero bytes and never finish, so a caller waiting on the stream reads a slow response rather than a failure.

What to check:

  • The output of node --version on hosts, CI runners and inside each container image, since a package inventory will not show a runtime baked into an image.
  • Whether Express 5 or body-parser 2.x is in the dependency tree, because both decode Content-Encoding: br request bodies.
  • Any fetch() call or webhook delivery that reaches a host outside the deployment, since fetch decompresses Brotli responses automatically.
  • Whether the process has a restart supervisor, because a restart loop gives an attacker repeated attempts at the memory offset.
  • Where core dumps are written, and who can read them.

What would change the assessment: A public exploit that works across Node.js builds without a per-build offset would move this to the top of the issue. Every chain seen so far encodes an address specific to one binary. ELLIO measured that the offset for 24.22.0 produced nothing in 10,000 attempts against 22.23.3 and 26.12.0.

GitLab’s regexp compiler overflows on a CI lint request

What happened: The integer overflow is in Ruby’s Onigmo regular expression compiler, which GitLab reaches through its CI lint endpoint. GitLab’s advisory describes an unauthenticated user executing arbitrary code through a specially crafted regular expression in a CI/CD configuration. It scores the bug 10.0 with a scope change, the only score in this issue that reflects a compromise of the host rather than of the process. The fix arrived on 12 October in 19.4.2, 19.3.5 and 19.2.8. Ruby shipped the underlying compiler fixes in 4.0.9, 3.4.12 and 3.3.13 on 23 October.

The public exploit path narrows the advisory’s wording. The Metasploit module merged on 30 October needs credentials for a user who can push to a public project. It plants a crafted CI configuration and repository path, then sends the unauthenticated CI lint request that compiles the expression and runs a command as the git account. Each attempt allocates slightly more than 2 GB and crashes a Puma worker, so the technique is loud and needs a target that will come back up. The module carries offsets for Ruby 3.3, which the advisory names as the standard production runtime.

GitLab shipped six threat detections with the patch. They cover Onigmo worker crashes, hostile regexp in GraphQL CI lint, oversized non-ASCII paths, guard rejections, Workhorse crashes and scripted client error bursts. A crash-based detection is only useful if it is alerting, and publishing these with the patch says what the vendor expected.

What it reaches: Self-managed GitLab Community and Enterprise instances on the affected lines. GitLab.com and GitLab Dedicated were patched before the public release, so the exposed population is the self-managed estate. That estate includes instances holding the source and CI definitions for other organizations. It is what makes a GitLab compromise a trust-chain event rather than a single-service outage, since the repository is the artefact everything downstream is built from.

What to check:

  • The exact GitLab version, since the affected ranges are 19.2 before 19.2.8, 19.3 before 19.3.5 and 19.4 before 19.4.2, and earlier major lines fall outside the advisory.
  • Whether any public project accepts pushes from users outside the organization, because that is the credential the public exploit requires.
  • Whether the Onigmo worker crash detection is alerting, since a failed attempt crashes a Puma worker as readily as a successful one.
  • Whether the instance is reachable from the internet at all, which changes the priority rather than the answer.

What would change the assessment: A demonstration that the CI lint endpoint compiles attacker-supplied expressions for a project the attacker cannot push to would remove the credential step. That would make this the most serious story here. No reviewed source shows it, and the module’s documentation treats the push requirement as necessary rather than incidental.

The common thread is a scheduling problem rather than a technical one. All three fixes are version bumps in components that application teams do not own. Two carry a catalog deadline of three days, which is shorter than most change advisory processes take to approve anything. The Brotli fix is the one most likely to still be outstanding next week. A decoder is vendored into runtimes, language packages and container images, and no single inventory lists all of them.