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.

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_limitlow" 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
- 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. - 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.
- Stamp it with a unique header marker and note the time, so you can find the request in the logs three minutes later.
- While it runs, watch the FPM error log, the pool status page (
pm.status_path) if you have one, and the upstream error rate. - 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

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 11orexited on signal 6from a child at a timestamp that lines up with an inbound request is the primary signal.Allowed memory size exhaustedon its own is noise. - Kernel log.
dmesgor the system journal records the segfault with thephp-fpmbinary and the faulting address. Keep that line; it is the artifact a second researcher will ask for. - Pool saturation.
max_childrenreached, a non-zero listen queue, andseems busywarnings 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
- 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. - Map the pools. For each FPM pool record
memory_limit,pm.max_children, and whether it fronts an application that accepts unauthenticated POSTs. - Write the hypothesis down in the form above, naming the host, the pool, and the request shape.
- Run the boundary test on a canary with a single worker, so log attribution is unambiguous.
- Capture three artifacts per reproduction: the request (method, path, headers, body hash), the log line with child PID and timestamp, and the response bytes.
- 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.
- Document any
memory_limitworkaround. If the value was tuned low as a mitigation, record why it was set and what changed. Do not leave it as tribal knowledge. - 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
- Unit 42, Threat Brief: Critical Vulnerabilities in PHP (CVE-2026-109618, CVE-2026-109619) — https://unit42.paloaltonetworks.com/cve-2026-109618-cve-2026-109619/
- NVD, CVE-2026-109618 — https://nvd.nist.gov/vuln/detail/CVE-2026-109618
- NVD, CVE-2026-109619 — https://nvd.nist.gov/vuln/detail/CVE-2026-109619
- PHP, supported versions and end-of-life dates — https://www.php.net/supported-versions.php
- CISA, Known Exploited Vulnerabilities Catalog — https://www.cisa.gov/known-exploited-vulnerabilities-catalog
- OWASP Web Security Testing Guide — https://owasp.org/www-project-web-security-testing-guide/
- Trusteed CTEM — https://trusteed.io