groundy
Security

How to Block PHP2Shell (CVE-2026-109619) Until Your Patch Lands

The AWS PHP2Shell WAF rule blocks the exact POST, PHPSESSID, and PHP_SESSION_UPLOAD_PROGRESS conjunction behind CVE-2026-109619, at the cost of legitimate upload-progress.

Published 7 references
On this page12 sections

If you run a public PHP application and cannot schedule an upgrade before the weekend, deploy the edge rule now and accept that your upload-progress bars will break until the patch lands. That is the short version. On October 21, 2026, PHP shipped an emergency release fixing two critical remote code execution flaws in session upload progress handling, and Amazon Threat Intelligence had already observed an exploitation attempt on October 17, four days before public disclosure, per Tenable’s FAQ on the php2shell vulnerabilities. AWS responded with security bulletin 2026-143, which publishes a ready-made WAF rule named PHP2Shell. The rule is unusually well-aimed: its match conditions are exactly the preconditions the vulnerable code requires, which independent source analysis confirms. But it has a cost the bulletin does not mention, and it does not cover every attack chain. This article translates the rule into plain match logic, prices the tradeoff, and lays out how to detect attempts and retire the block once you upgrade.

Why the patch window is the real problem

The two CVEs at the center of this are CVE-2026-109618, an out-of-memory corruption issue, and CVE-2026-109619, a stale global state and use-after-free flaw in PHP’s session upload progress handler. PHP’s security team rates both at CVSS v4.0 9.2 and says each can allow unauthenticated remote code execution, according to Tenable’s writeup. The same source reports that php2shell is now being broadly exploited in the wild. CVSS scores differ across sources, so treat 9.2 as the PHP team’s rating rather than a settled consensus number.

The exposure is wider than many teams assume, because the vulnerable feature is on by default. Datadog Security Labs’ analysis states that PHP enables session upload progress by default and that it runs before the target script executes, even if the application never calls session_start(). The affected server API inventory covers mod_php, PHP-FPM, php-cgi, FrankenPHP, and LiteSpeed. Fixed versions are PHP 8.2.35, 8.3.36, 8.4.27, and 8.5.12, with source backports published for end-of-life branches 7.4, 8.0, and 8.1, per Datadog.

Updating is the only complete fix. Both Tenable and Datadog are explicit on this point; Datadog’s phrasing is that mitigations circulating in the community “don’t fully protect you.” So the question is not whether to patch, but what to do during the days or weeks between now and your maintenance window. That gap is what a virtual patch fills.

The AWS PHP2Shell rule in plain language

AWS security bulletin 2026-143 ships a custom WAF rule named PHP2Shell targeting CVE-2026-109619. Reading the JSON, the rule blocks a request only when all of the following hold:

  1. The HTTP method is exactly POST.
  2. The string PHPSESSID appears either as a cookie key (match scope KEY, across all cookies) or in the request body.
  3. The string PHP_SESSION_UPLOAD_PROGRESS appears in the request body.

The action is Block. All matches use PositionalConstraint CONTAINS or EXACTLY with no text transformations (Type: NONE), and oversize handling is set to CONTINUE on body and cookie inspections, meaning the WAF still inspects oversized payloads rather than skipping them. Visibility is enabled: sampled requests and CloudWatch metrics under the metric name PHP2Shell, per the bulletin’s VisibilityConfig block.

Why this conjunction and not something broader? Because it mirrors the trigger precondition of the bug itself. RemiPelloux’s independent source-level analysis of the vulnerable rfc1867 handler puts it directly: “The request must carry both a session id (cookie or PHPSESSID field) and the progress field, before the file part. Miss either and nothing happens at all.” The AWS rule matches precisely that conjunction. A request missing either field cannot reach the vulnerable code path, so blocking requests that carry both is a tight virtual patch rather than a blunt one. This is the part of the story the advisory coverage skipped: the rule is not a heuristic guess, it is a transcription of the bug’s own entry conditions into WAF logic.

Porting the signature to other WAF stacks

If you are not on AWS WAF, the signature ports cleanly as a logical AND of three conditions. Datadog notes that “controls at a reverse proxy or another boundary in front of PHP can block requests before they reach the vulnerable parser,” which is the general principle behind any port.

For ModSecurity or a compatible engine, express it as a chained rule: match method POST, then require PHPSESSID in request cookies or the request body, then require PHP_SESSION_UPLOAD_PROGRESS in the body, action deny. For nginx, the 0x4148 lab’s defense-in-depth notes suggest keeping fastcgi_request_buffering enabled so the full request body is buffered before proxying, per the CVE-2026-109618 php2shell lab; a Lua or njs script can then apply the same three-condition check. Wherever you port it, preserve two properties from the AWS original: inspect the body even when oversized (the CONTINUE behavior), and emit a dedicated metric so you can count hits separately from your generic WAF noise.

One porting caution deserves emphasis. The AWS rule applies no text transformations, so case-mutated or encoded field names may not match [unverified]. More fundamentally, the WAFFLED paper on WAF evasion (arXiv:2503.10846) shows that rule-based WAFs can be bypassed through parsing discrepancies between the WAF and the backend, “not… obfuscating the payload itself, but… how WAFs interpret HTTP requests before applying their rulesets.” If your WAF parses multipart bodies differently than PHP does, an attacker may be able to construct a request your WAF sees as benign and PHP sees as a match. The supplied evidence does not test whether any specific discrepancy defeats this particular rule, so treat the port as a strong filter, not a guarantee, and verify with your own test requests before trusting it.

The tradeoff the bulletin omits: your progress bars match the same signature

Here is the cost AWS’s bulletin does not discuss. The conjunction the rule blocks, POST plus a PHPSESSID cookie plus PHP_SESSION_UPLOAD_PROGRESS in the body, is exactly what a browser sends when an application legitimately uses PHP’s upload progress feature to render a progress bar. That is not a measured false-positive rate; it is a bounded inference from the rule semantics and the documented trigger precondition. No supplied source quantifies how much legitimate traffic the rule drops in the wild. But if your application, or a plugin in it, implements upload progress, that feature will stop working the moment you deploy the block.

I would deploy it anyway, with one condition: check first whether anything you ship actually uses the feature. In my reading of the evidence, the risk asymmetry favors blocking, because exploitation is reportedly active and the vulnerable handler runs by default, per Datadog. But the honest framing is that this rule shifts the burden from patch scheduling to edge policy. You are buying time for your maintenance window by temporarily disabling a PHP feature at the boundary, and the price of that time is any user-facing progress indication that depends on it. If a broken progress bar on a high-traffic upload flow is unacceptable, the alternative is the config-level mitigation below, which has the same feature cost but applies server-side.

Exposure triage before you deploy

Work through these checks in order; each one changes what you should do.

CheckWhat to look forWhat it changes
PHP versionAnything below 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12Unpatched: you need a compensating control now. One analysis claims every version from 5.4.0 to 8.5.11 is affected, but that comes from a single researcher and is [unverified]
SAPI in usemod_php, PHP-FPM, php-cgi, FrankenPHP, LiteSpeedAll are in Datadog’s affected inventory; mod_php specifically changes your coverage calculus (see below)
session.upload_progress.enabledDefault is enabled, and it runs pre-script even without session_start()If you can set it to 0, do so; PHP’s advisory lists this as exposure-reducing for CVE-2026-109619
File uploads + sessions in the appMultipart POST endpoints reachable unauthenticatedThese are the paths the AWS rule protects; prioritize them in WAF scope
Upload-progress UI in useAny feature relying on PHP_SESSION_UPLOAD_PROGRESSThis is your false-positive surface; expect it to break under the block

Two compensating controls sit alongside the edge rule, with different coverage and cost:

ControlCoverageCostResidual risk
AWS PHP2Shell rule (or ported equivalent)The session-upload-progress trigger conjunction, per the AWS bulletin and RemiPelloux’s analysisBreaks legitimate upload-progress traffic; WAF parsing discrepancies may allow evasionOther chains, including the mod_php OOM path, remain open
session.upload_progress.enabled=0 (plus fatal_error_backtraces=0 on PHP 8.5)Reduces exposure to CVE-2026-109619 paths, per TenableSame feature loss; requires restarting every PHP workerPHP states updating is the only complete fix
Upgrade to 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12All known chainsMaintenance window, regression testingNone documented in the supplied evidence

The sensible combination is rule plus config mitigation now, upgrade as soon as the window allows.

What the rule cannot cover

Two gaps matter, and both argue against treating the edge block as sufficient.

First, the mod_php OOM chain. One researcher’s dissection of the vulnerabilities describes a “Chain D (OOM): Targets any Apache mod_php installation. No application dependency at all,” a runtime-level path that requires no session ID and no PHP_SESSION_UPLOAD_PROGRESS field. A body signature keyed on those fields cannot see it. Treat this as a single-researcher claim: the same writeup’s broader assertions (five independent RCE chains, the 5.4.0-to-8.5.11 range) partly conflict with RemiPelloux’s repository, which reports failing to reproduce code execution for its mechanism and labels that RCE claim “retracted — memory safety only.” The safe reading is that a session-field signature cannot be assumed to cover chains that need no session fields, and if you run mod_php, the upgrade is more urgent for you than for a PHP-FPM shop behind a good WAF.

Second, evasion of the body match itself. The 0x4148 lab demonstrates a stall-based use-after-free trigger: a multipart POST with a session ID and PHP_SESSION_UPLOAD_PROGRESS, sent in two bursts separated by a pause longer than session.upload_progress.min_freq (1 second by default), per the lab’s writeup. That path still uses the same field names, so the rule catches it; timing is not the gap. The open question is whether renaming or omitting fields could still trigger the bug while slipping the match. RemiPelloux’s analysis suggests missing either field means “nothing happens at all,” which cuts against trivial evasion, but no supplied source tests mutation of the field names directly, so evasion remains [unverified]. Combined with the WAFFLED parsing-discrepancy results, the right posture is: the rule covers the documented trigger shape well, and you should not assume it covers shapes nobody has published yet.

Detecting attempts while the rule holds

You get telemetry for free if you deploy the AWS version: the rule emits sampled requests and a CloudWatch metric named PHP2Shell, per the bulletin. A rising count on that metric before your patch lands is your exploitation-attempt signal; a flat zero combined with user complaints about broken uploads is your false-positive signal. Watch both.

On the host side, Datadog’s detection guidance points to three log patterns: unusually large form submissions, memory-exhaustion errors during request initialization, and unexpected shell processes spawned from PHP workers. One subtlety worth internalizing: successful command execution does not necessarily crash the worker, so the absence of crashes is not evidence of absence. If your WAF metric shows blocked attempts and your PHP error log shows memory exhaustion on the same endpoints, assume the rule is catching real probes and accelerate the upgrade.

Retiring the block

Treat the edge rule as debt with a known payoff date. Once your fleet is on 8.2.35, 8.3.36, 8.4.27, or 8.5.12 (or a backported build for 7.4 through 8.1, per Datadog), roll the rule from Block to Count, confirm legitimate upload-progress traffic flows again, then delete it. Keep the config mitigation if it costs you nothing, but do not let either control linger as a substitute for the patch; PHP’s own advisory says configuration changes “reduce exposure to specific attack paths but do not make an installation safe,” per Tenable.

The durable lesson outlasts this CVE. A virtual patch is worth deploying when three things line up: exploitation is already observed, the signature sits exactly on the bug’s entry conditions rather than approximating them, and the false-positive cost is a feature you can live without for days. All three hold here. What the supplied evidence cannot settle is the rule’s measured false-positive rate, its real-world evasion resistance, and coverage of the chains that need no session fields at all. Deploy the block, restart your workers with upload progress disabled if you can, watch the PHP2Shell metric, and book the upgrade window anyway. The rule buys you the time to patch calmly; it does not buy you the option not to patch.

Frequently Asked Questions

Which PHP versions fix the php2shell vulnerabilities?

Fixed versions are PHP 8.2.35, 8.3.36, 8.4.27, and 8.5.12, with source backports published for end-of-life branches 7.4, 8.0, and 8.1, per Datadog.

What legitimate feature breaks when you deploy the PHP2Shell WAF rule?

The conjunction the rule blocks, POST plus a PHPSESSID cookie plus PHP_SESSION_UPLOAD_PROGRESS in the body, is exactly what a browser sends when an application legitimately uses PHP’s upload progress feature to render a progress bar.

How do you retire the PHP2Shell block after upgrading?

Once your fleet is on 8.2.35, 8.3.36, 8.4.27, or 8.5.12 (or a backported build for 7.4 through 8.1, per Datadog), roll the rule from Block to Count, confirm legitimate upload-progress traffic flows again, then delete it.

References

Follow the links in the article for context. The supporting material is collected here for further reading.

  1. Tenable's FAQ on the php2shell vulnerabilitiestenable.comAccessed
  2. Datadog Security Labs' analysis of php2shellsecuritylabs.datadoghq.comAccessed
  3. AWS security bulletin 2026-143aws.amazon.comAccessed
  4. RemiPelloux php-session-upload-progress-uafgithub.comAccessed
  5. 0x4148 CVE-2026-109618 php2shell labgithub.comAccessed
  6. WAFFLED paper on WAF evasion (arXiv:2503.10846)arxiv.orgAccessed
  7. qideng: php2shell dissecting PHP's critical vulnerabilitiesqideng.substack.comAccessed

Join the discussion

Share a useful perspective or ask a question about this article.

Discussion guidelinesComments privacy