← Back to blog
Blog Detail

PHP2Shell CVE-2026-109618: Testing the memory_limit Boundary to Prove Impact

CVE-2026-109618 sits in PHP's out-of-memory handler, so a tight memory_limit moves requests toward the bug rather than away from it. Drive one FPM pool to its ceiling and read whether the worker survived.

Trusteed Team
Trusteed Editorial
Written On
Oct 30, 2026
Category
Research
Read Time
9 min read
  • PHP
  • CVE-2026-109618
  • PHP2Shell
  • Vulnerability Validation
  • Memory Safety
  • AppSec
  • Trusteed
PHP2Shell CVE-2026-109618: Testing the memory_limit Boundary to Prove Impact

PHP2Shell CVE-2026-109618: Testing the memory_limit Boundary to Prove Impact

TL;DR

CVE-2026-109618 sits in PHP core's out-of-memory handler: allocation failure interrupts a request while internal request data is only partially initialized, and cleanup can then treat invalid memory as valid. The test that answers exposure on your own hosts is a boundary test — push one FPM worker to its memory_limit with an unauthenticated request and read what the process did. Allowed memory size ... exhausted with the worker still alive is a different outcome from child <pid> exited on signal 11 at the same timestamp. Only the second is a finding you can hand to someone else. Version banners and closed patch tickets do not answer that question.

What this is

A memory-limit boundary test is a runtime test, not a source review. You pick one PHP pool, put a request on it that pushes allocation to the configured ceiling, and record the process state at the moment allocation fails.

The vulnerability sits in the failure handler, so the failure is the test surface: which allocation path trips, whether cleanup runs against partially initialized request data, and whether the worker survives.

For CVE-2026-109618 and its sibling CVE-2026-109619 — grouped publicly as PHP2Shell, both rated 9.2 under CVSS 4.0 by Unit 42 — that reframes the work. You are not hunting an upload feature or a specific framework route. You are looking at the layer every request passes through before your application code runs, and asking what it does when it runs out of room.

Why it matters

If you run PHP, the question is already yours: shared hosting pools, CMS fleets, internal tools running on long-lived images, appliances with an embedded web UI. Unit 42 names the blast radius directly — WordPress, Drupal, Magento and the wider ecosystem inherit the flaw because it sits underneath them.

Two things fail when the boundary is never tested:

  • The scope line. A patch ticket is opened against a version string from a scanner. Nobody establishes which pools actually reach the OOM handler, so nobody can say whether the patch landed where it matters — or whether a forgotten branch on a marketing host is still serving.
  • The control you think you have. "We cap memory_limit low" feels like hardening. The bug lives in the out-of-memory path, so a tight limit moves requests toward the vulnerable handler rather than away from it.

Availability is the floor here, not the ceiling. Corruption inside the OOM handler is the same class of bug that ends in code execution once it is weaponized reliably.

How it works

CVE-2026-109618. Out-of-memory handling interrupts PHP while internal request data is only partially initialized. Request cleanup then runs, and can treat invalid data as valid.

CVE-2026-109619. Stale internal state is reused after its backing memory has already been freed, before normal initialization resets it.

Both sit in PHP core, so the affected range is defined by the advisory, not by a banner. Take the range from the Unit 42 brief and the NVD entries linked in Sources before you write a scope line, then check the PHP supported-versions page for which branches can still receive a fix at all — an end-of-life branch has no upstream patch to wait for.

The hypothesis

Write it as one testable statement before you touch a target:

An unauthenticated request that drives this pool to its memory ceiling reaches the OOM cleanup path, and the worker does not survive it.

Designing the test

  1. Pick one host you own with a single PHP-FPM pool and a small pm.max_children, on a canary or staging instance that mirrors production config.
  2. Build a request that expands inside PHP rather than on the wire: deeply nested form fields, a body with many parts, a JSON document that nests arrays. You want allocation amplification, not a large upload.
  3. Stamp it with a unique header marker and note the time, so you can find the request in the logs three minutes later.
  4. While it runs, watch the FPM error log, the pool status page (pm.status_path) if you have one, and the upstream error rate.
  5. Repeat at least twice, once with the marker changed. A crash you cannot reproduce is not evidence.

Reading the result

Observation at the moment of failure What it supports
Allowed memory size of N bytes exhausted fatal, worker PID unchanged afterwards The request died before the corrupted cleanup path. Not immunity — a different body shape may still land.
child <pid> exited on signal 11 at the same timestamp as your marker The memory-safety failure was reached on unauthenticated input, and you have a reproduction.
Upstream 502/504 while the pool reports max_children reached Availability impact: one request per worker takes the pool out.
Response body contains request data you did not send, or malformed fields Partial-initialization evidence. Capture the bytes verbatim.

If the third row reproduces, you have a finding that needs no exploit chain. Illustrative example: a pool with five workers goes dark after five requests, from anywhere, with no credentials. Run that arithmetic against your own pm.max_children.

Detection and response

Dark Trusteed card comparing two PHP-FPM log lines at 17:22:07: one worker logs &amp;#39;Allowed memory size of 134217728 bytes exhausted&amp;#39; and stays alive, the other exits on signal 11 — proving the CVE-2026-109618 memory-safety failure is reachable from unauthenticated input.

The telemetry you need already exists on most hosts. The problem is that nobody reads it as a memory-safety signal.

  • PHP-FPM error log. exited on signal 11 or exited on signal 6 from a child at a timestamp that lines up with an inbound request is the primary signal. Allowed memory size exhausted on its own is noise.
  • Kernel log. dmesg or the system journal records the segfault with the php-fpm binary and the faulting address. Keep that line; it is the artifact a second researcher will ask for.
  • Pool saturation. max_children reached, a non-zero listen queue, and seems busy warnings during a period with no traffic spike to explain it.
  • Edge and proxy logs. A POST to an endpoint that only serves GET, or bodies structurally wrong for the route. Body-size caps and multipart rules in a WAF filter syntax the attacker controls; count them, don't bank on them.

What a defender checks first: whether crash timestamps cluster on one pool, and whether that pool's memory_limit is tighter than its neighbours. Tight limits correlate with exposure here, which is the opposite of the usual hardening intuition.

What to do

  1. Inventory the runtime, not the banner. Read the PHP binary version on each host and inside each container image (php -v, package metadata, the actual binary path when images are built from several layers). One host can run two.
  2. Map the pools. For each FPM pool record memory_limit, pm.max_children, and whether it fronts an application that accepts unauthenticated POSTs.
  3. Write the hypothesis down in the form above, naming the host, the pool, and the request shape.
  4. Run the boundary test on a canary with a single worker, so log attribution is unambiguous.
  5. Capture three artifacts per reproduction: the request (method, path, headers, body hash), the log line with child PID and timestamp, and the response bytes.
  6. Retest after the patch with the same request shape. A version bump is not a result; a worker that stays up under the same load is.
  7. Document any memory_limit workaround. If the value was tuned low as a mitigation, record why it was set and what changed. Do not leave it as tribal knowledge.
  8. Report the availability case honestly. If you cannot reach code execution, say so, and attach what you did prove: unauthenticated worker loss, steps to reproduce, and the affected pool.

FAQ

Does a low memory_limit protect me from CVE-2026-109618? No. The flaw is in the out-of-memory handler, so a lower ceiling makes the handler easier to reach. Treat memory_limit as a performance setting, not a control, and test the boundary instead.

Do I need a file upload feature to be exposed? No. The trigger is request parsing in PHP core, which happens before your application routing or framework code runs. Any endpoint that accepts a POST body is in scope for the test.

Is this only a denial-of-service issue? Both PHP2Shell CVEs are rated as remote code execution. What you can usually prove quickly is worker loss on unauthenticated input; a code-execution proof depends on a working corruption strategy for your specific build.

How do I know which PHP versions are affected? From the advisory, not a banner. Read the Unit 42 brief and the NVD entries in Sources, then confirm which branches your distro or image still maintains.

Can I test this in production? Run the boundary test on a canary that mirrors production config. If you must test live, use one worker and a scheduled window — a successful test takes that worker out of service.

Where this fits in a program

A scanner that flags hosts carrying the vulnerable PHP binary has produced a signal, not a finding. The boundary test above is what turns one into the other. Trusteed CTEM is built around that gate: inventory the assets, test the hypothesis against the live application, and hold the result to exploitability and business context before it becomes an alarm. The reproduction is still yours to run.

Sources

Join Our Newsletter

Trusteed keeps you informed: emerging risks, platform updates, and practical guides for faster defense.