PHP2Shell: What Happened, Why It Happened, and What to Do Now
Originally published on ryanverwey.dev.
The immediate answer
The PHP security emergency unfolding on October 21, 2026 concerns PHP2Shell, two critical vulnerabilities in the PHP runtime: CVE-2026-109618 and CVE-2026-109619. Both can enable unauthenticated remote code execution under exploitable conditions. PHP has released fixes. If you operate a PHP website, verify the PHP runtime actually serving its requests and update it immediately. A WordPress, Drupal, or plugin update alone is not the same operation. PHP's release advisory
For operators in a hurry: establish an owner for every PHP deployment, apply a verified fixed package or image, replace old running workers, and investigate suspicious activity independently of patching. If you cannot remediate an exposed application promptly, consider isolating it from public access. That isolation recommendation is also present in the European agencies' joint statement.
This is a developing incident, not a completed forensic history. This article is a publication-day assessment dated October 21, 2026, Eastern Time. It separates maintainer-confirmed behavior, researchers' demonstrations, providers' observations, and my own engineering recommendations. It does not claim to have tested an exploit or investigated a victim organization. The diagrams are original explanatory illustrations, not screenshots of attacks or measurements of compromise.
What actually happened, and what remains unproven
Calling this “the PHP hack” compresses several different events into one phrase. Researchers established serious flaws in PHP. Maintainers coordinated emergency fixes. Exploit demonstrations and public attention accelerated. Providers began deploying defenses, while some operators temporarily withdrew services.
These categories matter because each answers a different question:
| Evidence category | What it establishes | What it does not establish |
| --- | --- | --- |
| A vulnerable version is installed | A remediation or vendor-validation task exists | That an attacker reached the installation |
| A researcher demonstrates execution | A particular setup can be exploited | That every deployment is equally exploitable |
| A security provider sees attack traffic | Attempts are reaching monitored infrastructure | That every attempt succeeded |
| A service goes offline | Availability changed | Whether the cause was compromise or precaution |
| Forensic evidence confirms attacker activity | A specific system was compromised | The total number of victims worldwide |
Cloudflare says it observed attacks against its customers and changes to payloads intended to evade protection. It says its PHP2Shell protections became available by default for customers using its free or paid WAF at 03:02 UTC on October 21. Those are the provider's observations and protection claims, not a census of successful breaches. Cloudflare's incident report
Wiz reports demonstrating unauthenticated code execution against the WordPress, Drupal, and Joomla container images it tested. It also describes memory disclosure and multiple variants grouped under the two CVEs. This is significant evidence of practical exploitability, but a demonstration against a named application's deployment does not mean that application's own code introduced the flaw. Wiz Research
There are also documented precautionary outages. Email-alias service addy.io says it took its website and web application offline while updating, then restored them; email delivery continued. Its explanation describes a protective shutdown, not a confirmed intrusion. addy.io's account
I could not establish a reliable global victim count from the primary sources reviewed. Claims that a percentage of “the internet was hacked” confuse technology adoption with confirmed compromise. We also should not infer a particular criminal group, motive, or data-theft campaign without evidence tying it to an intrusion.
The disclosure timeline
The PHP Foundation traces early signs to 2024 audit follow-up, before a concrete execution path was established. It attributes delays to resource constraints and difficult compatibility decisions. Reports from Calif, OpenAI, Wiz, and others later clarified the impact. PHP Foundation retrospective
| Date | Documented development |
| --- | --- |
| 2024 | Early signs of the OOM problem emerge during audit follow-up. |
| August 2026 | Concrete reports clarify the impact and intensify remediation. |
| October 20 | CVE identifiers circulate; PHP issues advance notice. |
| October 21 | Emergency releases and technical advisories become public. |
The Foundation describes leaked identifiers, an accidentally published demonstration, and premature distributor images. The planned October 22 release moved forward. This article reports that account without independently adjudicating participants' responsibility. Foundation disclosure account
!Illustrated sequence: early signs in 2024, concrete reports in August 2026, disrupted disclosure on October 20, and fixes released October 21.
Figure 1. A deliberately coarse timeline. Sources: the Foundation account above and the release notices below. It shows sequence, not the duration of confirmed malicious activity.
The pre-announcement scheduled release for 14:00 UTC, or 10:00 a.m. EDT. Publication-day summaries differ on the precise earlier availability time: CERT-EU records 13:30 UTC; ENISA records 13:40 UTC. The defensible statement is that fixes became available October 21, before the announced deadline, rather than presenting one minute as universally settled. PHP advance notice, CERT-EU, ENISA
Why these vulnerabilities are unusually consequential
PHP runs on the server. It processes requests and executes application code before sending a response to a browser. A problem in that runtime can therefore sit underneath many unrelated products. The UK NCSC explicitly tells organizations to inventory PHP throughout their infrastructure, including appliances and third-party services that bundle it. NCSC alert
The important architectural question is where the security boundary sits. An application login protects application actions after the application can enforce that login. It cannot reliably protect a vulnerable runtime operation that happens earlier. Likewise, a protection implemented inside PHP depends on PHP safely reaching that protection.
This is why I would challenge the reassuring statement “our admin page requires multifactor authentication.” MFA remains valuable, but it answers a different question. The relevant question here is whether untrusted network input can reach vulnerable PHP processing at all.
CVE-2026-109618: interruption during partially initialized state
PHP's OOM advisory explains that longjmp() can abort processing after an allocation failure while internal request data is only partly initialized. Cleanup can then consume invalid state. It documents unauthenticated remote execution on Apache mod_php, including before application code runs. The fix changes OOM handling to defer a soft failure to a safe execution point, with a hard exit at a higher threshold. Maintainer OOM advisory
A useful conceptual model is an interrupted handoff: one part of the engine has made a structure reachable, but has not finished making it safe for another part to use. Cleanup assumes the handoff completed. This analogy explains the lifetime problem; it is not a description of a payload.
CVE-2026-109619: a later request uses yesterday's pointer
The second advisory describes request-owned memory that is freed while a reusable worker retains references to it. An operation in a later request can consume that stale state before normal initialization replaces it. Exploitation requires a suitable producer and consumer, with requests reaching the same worker. The advisory confirms remote execution through session-lifecycle paths on both mod_php and PHP-FPM. Maintainer stale-state advisory
Think of a reusable worker as a desk used for successive jobs. Clearing the old paperwork is not enough if the desk still contains instructions to use an old location. That location may now contain something else. The mistake is not simply “too much traffic”; it is treating an invalid reference as trustworthy state.
!Two broken handoffs shown with layered blue structures: interrupted initialization reaches cleanup; freed memory leaves a stale reference for a later request.
Figure 2. Simplified models of the two maintainer advisories. The sculptural forms illustrate lifecycle stages, not a runnable exploit or a complete list of variants.
Configuration differences affect the path, not the patch obligation
The PHP project explicitly includes PHP-FPM, mod_php, CGI, FrankenPHP, and LiteSpeed in its warning. It assigns the highest risk to mod_php. PHP release advisory
There is a publication-day documentation inconsistency worth preserving: the OOM advisory says a complete FPM exploit was not demonstrated for that SAPI, while the Foundation's narrative says OOM-related FPM execution was confirmed through application paths. The separate stale-state advisory clearly confirms an FPM session-lifecycle exploit. FPM must not be treated as safe. Exact demonstration claims should name their CVE and source. OOM advisory, Foundation account, stale-state advisory
Who is affected, and which versions contain fixes?
Both issues are rated CVSS v4.0 9.2, Critical. A severity score is not a probability that your server has been compromised. The supported-branch remediation thresholds published for this incident are: CERT-EU advisory
| PHP branch | First upstream release with these fixes |
| --- | --- |
| 8.2 | 8.2.35 |
| 8.3 | 8.3.36 |
| 8.4 | 8.4.27 |
| 8.5 | 8.5.12 |
Earlier builds in each branch require remediation or a verified vendor backport.
!A joined blue slab displays the fixed releases: 8.2.35, 8.3.36, 8.4.27, and 8.5.12. Older branches require a verified vendor fix or migration.
Figure 3. Publication-day patch reference. Source: CERT-EU. These are minimum fixed releases for this incident, not a recommendation to avoid later security updates.
PHP also supplied minimal source backports for 7.4, 8.0, and 8.1. That exceptional assistance does not restore normal upstream support. Distribution and commercial support packages may use their own revision numbers, so compare the complete package revision with the vendor's advisory rather than rejecting or accepting it solely from the upstream-looking number. PHP's update instructions
PHP's published lifecycle gives branches two years of active support followed by two years of security support. An emergency fix is therefore a reason to review the next upgrade deadline, not to reset your support assumptions. PHP support policy
Wiz traces affected code as far back as PHP 5.4, while describing different requirements across variants. Treat legacy systems as an inventory priority; do not interpret “our version is old” as immunity. Wiz technical research
For embedded products, consult the vendor and the NCSC-NL coordination repository. A missing product entry is not evidence of safety. Record the vendor's actual determination, its date, and the affected product version.
Our take: could this have been prevented?
The honest answer has three parts. Runtime design and security governance could have reduced the chance or duration of the vulnerability. Deployment controls could have reduced exposure. Containment could have reduced damage after exploitation. None supports a claim that an individual administrator could have patched an unavailable release.
The following is my engineering analysis, not a claim that these measures were tested against every PHP2Shell variant.
What AI changes, and what it does not
Wiz describes frontier AI as helping researchers turn difficult, longstanding engine flaws into practical demonstrations. That is a claim about the research process; it does not establish that these defects were originally written by AI or that every malicious attempt is AI-operated. Wiz's research account
My interpretation is that faster discovery increases the value of a fast, reliable response system. A tool that finds more defects does not automatically supply the maintainers, compatibility decisions, distribution coordination, or deployment capacity needed to remove them. Defenders should measure time to a verified fix, not celebrate report volume as though it were the same outcome.
Nor should “AI found it” become a reason to skip validation. A security report still needs reproducibility, a clear affected configuration, an impact assessment, and a reviewed correction. The useful investment pairs better discovery with the human and operational capacity to act on credible findings.
1. Treat exceptional control flow as part of the security model
Most application tests ask whether valid input produces the correct output. Runtime tests also need to ask what remains true when execution stops at the worst possible point. Allocation failure, timeout, error callbacks, shutdown hooks, and worker reuse deserve explicit attention.
For systems with manual memory management, I would make ownership and initialization invariants reviewable: who owns a structure, when it becomes visible, which exit paths can interrupt its construction, and who may still reference it after teardown. Fault injection should deliberately fail allocations across those transitions. Sanitizers and fuzzing can help reveal violations, but passing a test campaign cannot prove that all memory-safety defects are absent.
A useful review exercise is to walk each lifecycle backward. If cleanup runs now, what does it assume has already happened? If a second request starts now, what state does it inherit? That method targets the kind of boundary error illustrated here without needing a weaponized reproduction.
2. Give difficult security decisions an escalation deadline
When every proposed fix has compatibility or availability costs, consensus can become a waiting room. My recommendation is to assign one accountable decision owner, require written tradeoffs, set a deadline for escalation, and reserve engineering capacity for high-impact unresolved risks.
The goal is not to pretend that maintainers can fix everything instantly. It is to prevent “we cannot agree on the least harmful change” from silently becoming a long-term security policy. An organization that depends on an open-source runtime should fund maintenance and review capacity as part of its operating cost.
3. Make emergency rollout a rehearsed product capability
A team should be able to answer where PHP runs, who can update it, how a fixed build enters production, how old workers are retired, and what proves completion. Those answers should exist before a headline arrives.
I would rehearse a runtime update using a small representative deployment, then expand with observable health checks. The rollback plan must be explicit: reverting to a known-vulnerable runtime may restore availability while reopening the security exposure. A better fallback may be isolation or a reduced service built on a verified fixed image.
4. Reduce what the PHP process can reach
Successful code execution has the privileges and access of the compromised process; it is not automatically root access to every connected system. That distinction creates a concrete design opportunity. Use narrowly scoped database accounts, separate service identities, limited filesystem write access, and restricted network paths.
For example, a public content site should not need a database credential that can administer every tenant. A web container should not need a mounted container-engine socket. A content upload directory should not become an unrestricted program-execution location. These are examples of reducing consequences, not assertions about what happened to a particular victim.
!A PHP worker connects through access boundaries to files, credentials, and a network. Limiting permitted access can reduce potential impact.
Figure 4. Original defensive threat model. Orange paths show possible access if a process is compromised. They do not assert lateral movement, root access, or data theft in this incident.
What to do if you think you are impacted
Run two tracks together: remove the vulnerability and determine whether it was used. Do not make one the completion criterion for the other.
Step 1: identify the real runtime and the accountable operator
Start with public websites, but include internal tools, staging sites, old subdomains, disaster-recovery instances, containers, and appliances. For each deployment, record its hostname, environment, owner, web-server integration, PHP version, package revision or image digest, and exposure path. The NCSC's guidance includes both infrastructure inventory and monitoring vendors for bundled-PHP updates. NCSC alert
With authorized shell access, these read-only commands provide a starting point:
php -v
php --ini
They report the invoked command-line binary and its configuration. They do not prove that Apache, an FPM pool, another container, or every node behind a load balancer is using that binary. PHP documents both switches in its command-line reference. Its configuration documentation also explains that configuration loading depends on how PHP runs.
Use authenticated administration or deployment inspection to establish the web runtime. Avoid leaving a public diagnostic page online: phpinfo() can reveal configuration, paths, headers, and environment information. PHP's phpinfo documentation
On managed hosting, ask for a precise answer: “Which package revision fixes both CVEs on my account, when was it deployed, and have all serving workers been replaced?” A generic message that the provider is monitoring the situation is not evidence that your instance is patched. WordPress explicitly tells site owners to check with their host and update PHP. WordPress security notice
Step 2: patch what is running, not just what is stored
My recommended completion record contains the old revision, the fixed revision, the deployment time, the worker replacement time, and the verification result. Keep one record per independently deployed service rather than a single checkbox for the whole estate.
For containers, rebuild or select a verified fixed image and redeploy the application. Updating the host's PHP package does not establish what is inside an image. Cloud.gov's incident instructions specifically distinguish its PHP buildpack from customer-managed container images and tell the latter group to update and redeploy. Cloud.gov guidance
For long-running workers, verify replacement rather than assuming that a package installer did it. PHP's configuration-loading behavior explains why editing a file alone is insufficient for server modules. PHP configuration lifecycle
Then check login, sessions, uploads, scheduled processing, and the application's main business transaction. These are practical smoke checks, not exploit tests. Verify every replica and the images used for future scale-out, or the next restart may reintroduce an old build.
Step 3: if patching is blocked, contain exposure deliberately
Prefer restricting access before requests reach PHP. A maintenance page rendered by the vulnerable application is a poor isolation boundary. Use the load balancer, reverse proxy, network controls, or an independently served static page, and account for alternate hostnames and direct-origin access.
The official configuration workarounds are partial. In abbreviated form, they include administratively enforcing memory_limit=-1 for the OOM path, disabling session upload progress before parsing, and disabling fatal_error_backtraces on PHP 8.5 for a specific stale-state path. Workers must be restarted. The advisory warns that underlying allocation failures and other variants remain possible; RLIMIT_AS and RLIMIT_DATA are inappropriate substitutes. CERT-EU mitigation details
Do not turn those settings into an unattended “fix everything” script. Removing PHP's normal memory ceiling changes availability risk. Request limits must be evaluated against real application behavior and the frontend's handling of decoded data. Use the linked vendor instructions for the full conditions, and assign an expiry and patch owner to temporary measures.
A WAF can buy time, but it is a separate control. Cloudflare itself says customers should still patch. Confirm that the applicable rules are enabled and that requests cannot bypass the protected entry point. Cloudflare guidance
Step 4: investigate evidence of exploitation
Begin by preserving the information you will need to reconstruct events. That can include proxy and web-server logs, PHP errors, host process telemetry, authentication events, outbound connections, deployment history, and relevant cloud audit records. Record collection time and timezone, protect the originals, and restrict access to evidence that contains sensitive data.
Elastic's PHP2Shell detection discussion emphasizes behavior such as unexpected processes launched by web services and suspicious follow-on execution. Those are useful investigative signals, but a detection rule needs the appropriate telemetry and can have blind spots. Elastic Security Labs
My recommended investigation questions are:
- Did a PHP or web-server worker start an unexpected shell, interpreter, or downloader?
- Did executable files, configuration, scheduled tasks, or administrator accounts change outside a known deployment?
- Did the service make unusual outbound connections or access secrets it normally leaves untouched?
- Do memory errors, worker crashes, or unusual requests correlate with those stronger signals?
- What evidence is missing because logging was disabled, incomplete, or already rotated?
A memory error by itself is not proof of intrusion. A suspicious request by itself is not proof of successful execution. Conversely, a quiet access log does not prove no compromise: it may not contain request bodies, process activity, or evidence from an alternate route.
Step 5: if compromise is suspected, recover trust
Escalate to the incident-response owner or a qualified responder. Isolate affected systems while balancing evidence preservation and ongoing harm. Avoid casually wiping or rebooting a suspected host before deciding whether volatile evidence is needed; do not leave an active intrusion connected merely to collect more logs.
For confirmed compromise, my recommendation is to rebuild from trusted sources after evidence collection, fix the entry path, and evaluate every credential accessible to the affected service. Rotate or revoke exposed secrets from a clean administrative environment. Consider database credentials, API tokens, service identities, signing keys, and sessions according to what the investigation establishes. Patching a runtime does not remove an attacker's separate persistence or invalidate a copied secret.
This workflow follows the broad separation of detection, response, and recovery in NIST SP 800-61 Revision 3; the PHP-specific operational checklist here is my synthesis. Decisions about customer notification, contractual duties, or regulatory reporting should go through the organization's incident and legal teams using the actual facts and applicable jurisdiction.
!Two separate response tracks: remediate through patching, restarting, and verification; investigate through preserving evidence, assessment, and recovery.
Figure 5. An original response checklist. Remediation and investigation proceed in parallel; a completed update is not a forensic clearance.
What this means for website owners and ordinary visitors
If you own a website but someone else manages its hosting, forward the two CVE identifiers and request instance-specific confirmation. Ask whether their response covers the origin, all PHP versions assigned to your account, and any staging or secondary sites. If they cannot provide a safe update path, discuss temporary isolation and a supported migration rather than relying on the fact that the homepage still works.
If you only visit a website, you cannot patch its server from your browser. An outage or maintenance banner does not establish that your personal information was stolen. Follow the operator's official security communications and any account-specific instructions. Do not install a “PHP repair” utility offered by a random message or submit credentials to an unsolicited emergency-checking page.
If an operator confirms credential or session exposure, follow its reset instructions through the legitimate site and address reused passwords on other services. This is contingent advice for confirmed or credibly suspected exposure, not a claim that every visitor to a PHP site now needs an emergency reset.
The lesson I would carry into the next incident
PHP2Shell makes a familiar operational weakness visible: a team may know its application very well while having a poor account of the runtime underneath it. The useful outcome is not simply a newer version number. It is a defensible chain of evidence from advisory, to affected asset, to fixed deployment, to verified running service, with a separate answer about possible intrusion.
My standard would be straightforward: name the owner, prove the revision, verify the workers, preserve the evidence, and state what remains unknown. That is a much stronger response than either minimizing a serious flaw or describing every vulnerable site as already breached.
For more context on how I approach implementation and operational tradeoffs, see my development background and the projects behind this portfolio.
Sources and reporting method
Primary sources were preferred over social posts and secondary headlines. Source statements were checked on October 21, 2026, Eastern Time. The list below is a reading trail; citations beside individual claims identify their basis. Source pages can change after publication. Differences in release timing and FPM demonstration language are explicitly retained above rather than silently reconciled.
1. PHP Security Team. (2026, October 21). Critical remote code execution vulnerabilities: update now. Release scope, fixed versions, and update guidance.
2. PHP maintainers. (2026, October 21). RCE via OOM corruption, GHSA-w8hw-w9fx-j79w. CVE-2026-109618 technical advisory.
3. PHP maintainers. (2026, October 21). RCE via stale global state, GHSA-6wmf-4rv5-p3rp. CVE-2026-109619 technical advisory.
4. The PHP Foundation. (2026, October 21). Today's PHP Security Release, and How We're Strengthening PHP Security. Maintainer-side history and disclosure account.
5. PHP Security Team. (2026, October 20). Upcoming Critical PHP Security Release. Advance notice.
6. CERT-EU. (2026, October 21). Security Advisory 2026-018. Severity, versions, and mitigation conditions.
7. ENISA and partner agencies. (2026, October 21). Joint statement on critical PHP vulnerabilities. Coordinated operational advice.
8. UK National Cyber Security Centre. (2026, October 21). Critical security issue affecting PHP. Inventory, monitoring, and isolation guidance.
9. Tzadik, S., Tamari, S., Ohfeld, N., & Schwartz, T. (2026, October 21). PHP2Shell technical research. Wiz's discoveries and demonstrations.
10. Molteni, D., Jones, D., & Nandwani, K. (2026, October 21). Cloudflare deploys protections against php2shell. Provider observations and WAF claims.
11. addy.io. (2026, October 21). Why addy.io went offline. First-party account of a precautionary outage.
12. Blackbourn, J. (2026, October 21). Critical PHP Security Release. Guidance for WordPress site owners.
13. Cloud.gov. (2026, October 21). Action required: Critical PHP vulnerabilities. Buildpack and container remediation responsibilities.
14. Elastic Security Labs. (2026, October 21). Assessing detection coverage for PHP2Shell. Behavioral detection considerations.
15. NCSC-NL. (2026). PHP2Shell coordination repository. Evolving third-party product information.
16. PHP documentation. Supported versions, command-line options, configuration loading, and phpinfo. Runtime-verification background.
17. Nelson, A., Rekhi, S., Souppaya, M., & Scarfone, K. (2025). NIST SP 800-61 Revision 3. General incident-response framework, not a PHP2Shell advisory.
Testing the AWS WAF Mitigations for PHP2Shell on Both CloudFront and an ALB
---
title: Testing the AWS WAF Mitigations for PHP2Shell on Both CloudFront and an ALB
published: true
description: 'I deployed the AWS WAF rules published for PHP2Shell (CVE-2026-109618 / 109619) on CloudFront (64 KB body inspection) and on an ALB (fixed 8 KB) with AWS CDK and measured inspection boundaries, false positives, per-layer thresholds and origin bypass.'
tags: 'aws, security, cdk, php'
cover_image: null
organization_id: 2794
id: 4897722
date: '2026-10-22T16:10:45Z'
---
Introduction
On 2026-10-21 the PHP project disclosed PHP2Shell (CVE-2026-109618 / CVE-2026-109619): CVSS v4.0 9.2, unauthenticated remote code execution (PHP security advisory, CERT-EU Security Advisory 2026-018).
As a stopgap until PHP can be upgraded, an AWS Security Blog post published four AWS WAF custom rules: PHP2Shell, Block-Multipart-FormData, Block-Oversize-Body and RateLimit-Posts. The AWS Security Bulletin also points to SizeRestrictions_BODY in AWSManagedRulesCommonRuleSet and to the PHP managed rule group (version 2.3, now the default), which adds protection for CVE-2026-109619.
Classmethod has already tested the AWS Security Blog rules thoroughly on an ALB, including the fact that PHP_SESSION_UPLOAD_PROGRESS placed after the first 8 KB of the body goes through.
This article reproduces that test on both CloudFront and an ALB and verifies what was still open:
- How far does raising CloudFront's body inspection limit (16 KB default, 64 KB max) get you?
- How does the ALB's Web ACL behind CloudFront affect the result?
- What changes when the origin only accepts traffic through CloudFront?
- How long does the rate-based rule take to react once the threshold is crossed?
📁 Code: waf-php2shell-mitigation (part of AWS CDK Reference Architectures).
> Note
> AWS WAF is a mitigation, not a fix. Upgrading to PHP 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 or later is the only complete remediation. PHP 8.1 and older are also affected but are end-of-life, so there is no official fixed release (the PHP team published minimal source backports, and some distributions and LTS vendors provide backported packages). Move to a supported version.
TL;DR
The AWS Security Blog rules are a stopgap until PHP is upgraded; the AWS Security Blog post itself states that the real fix is upgrading PHP. As a stopgap, the rules behaved as specified on both CloudFront and the ALB.
AWS WAF, however, inspects a request body only from the start up to a size limit: 8 KB on an ALB (fixed), 16 KB by default on CloudFront, up to 64 KB (AWS WAF documentation). On the ALB, a string ending at byte 8,192 was blocked; shifting it by one byte let it through. The Block-Oversize-Body rule from the same post (a size limit meant for CVE-2026-109618), when set to BLOCK, also stops requests whose bodies go past that limit.
How to configure that rule depends on your setup:
- If you use CloudFront, raise the inspection limit to 64 KB and block bodies larger than 64 KB with Block-Oversize-Body. The whole body is then inspected, and legitimate requests up to 64 KB still pass.
- With CloudFront + ALB + WAF, set the ALB's "block bodies over 8 KB" rule to COUNT and let the ALB accept traffic from CloudFront only. Left in BLOCK, the ALB stops legitimate requests that CloudFront already allowed. If the ALB Web ACL includes AWSManagedRulesCommonRuleSet, override its SizeRestrictions_BODY (blocks bodies over 8 KB) to COUNT for the same reason.
- If you expose only an ALB, blocking bodies over 8 KB is the baseline. According to the AWS Security Blog post, both CVEs sit on the multipart/form-data processing path, so an app that takes no file uploads and only accepts JSON over 8 KB could block Block-Multipart-FormData instead of using the size limit (not verified in this article). An app that accepts file uploads over 8 KB cannot be fully protected by WAF, so prioritize the PHP upgrade.
If your app changes session.name or session.upload_progress.name from the defaults, rewrite the rule strings to the actual names. Matching is case-sensitive, so this also applies when only the letter case differs.
Architecture
!Architecture
Client ─▶ CloudFront + Web ACL (us-east-1, 64 KB body inspection)
─▶ ALB + Web ACL (ap-northeast-1, fixed 8 KB)
─▶ Lambda container (nginx + PHP-FPM 8.4.27 + Lambda Web Adapter)
Client ─────────▶ ALB direct (comparison only, allow-listed IP)
A WAF decision does not depend on the PHP version behind it, so the origin runs a patched PHP 8.4.27 as a Lambda container image. On the PHP side, it applies these server-side mitigations from the AWS Security Blog post:
; php.ini
session.upload_progress.enabled = 0
memory_limit = -1 ; the real ceiling is the Lambda memory size
post_max_size = 8M
; PHP-FPM pool
pm = static
pm.max_children = 1
pm.max_requests = 1 ; replace the worker after every request
Telling which WAF and which rule acted, from HTTP headers
With WAF on both CloudFront and the ALB, a bare 403 does not tell you which layer answered. Every rule therefore names itself:
const ruleAction = (ruleName: string, action: RuleAction): wafv2.CfnWebACL.RuleActionProperty =>
action === 'BLOCK'
? { block: { customResponse: { responseCode: 403,
responseHeaders: [{ name: 'x-php2shell-blocked-by', value: `${label}:${ruleName}` }] } } }
: { count: { customRequestHandling: {
insertHeaders: [{ name: `php2shell-${label}-${ruleName.toLowerCase()}`, value: 'counted' }] } } };
BLOCK returns x-php2shell-blocked-by: cloudfront:PHP2Shell; COUNT inserts x-amzn-waf-php2shell-alb-oversize: counted into the origin request, and the PHP app echoes it back. Headers inserted by the CloudFront Web ACL travel through the ALB to the origin, and the BLOCK response header keeps its own name (no x-amzn-waf- prefix).
Note that clients can also send headers starting with x-amzn-waf-. If your application changes its behavior based on these headers in production, add a rule that blocks x-amzn-waf- headers arriving from outside, as the AWS WAF documentation describes.
Result 1: rule conditions (reproducing the ALB test on both layers)
| Request | via CloudFront | ALB direct |
|---------|----------------|------------|
| POST, cookie PHPSESSID + PHP_SESSION_UPLOAD_PROGRESS in body | 403 | 403 |
| POST, both strings in body | 403 | 403 |
| POST, PROGRESS only / cookie only | 200 | 200 |
| PUT, both strings in body | 200 | 200 |
| multipart PROGRESS field + cookie | 403 | 403 |
| lowercase cookie key phpsessid | 200 | 200 |
| lowercase php_session_upload_progress in body | 200 | 200 |
| JSON or text/plain containing both strings (false positive) | 403 | 403 |
The rule uses TextTransformation: NONE, so it only matches PHP's default names. If your app renames the session cookie or the upload progress field, change the rule strings too.
Result 2: string position vs inspection limit
Cookie PHPSESSID, with PHP_SESSION_UPLOAD_PROGRESS ending at byte N (oversize rule in COUNT):
| N | ALB direct | CloudFront KB_16 | CloudFront KB_64 |
|---|-----------|--------------------|--------------------|
| 8,192 | 403 | 403 | 403 |
| 8,193 | 200 | 403 | 403 |
| 16,385 | 200 | 200 | 403 |
| 65,536 | 200 | 200 | 403 |
| 65,537 | 200 | 200 | 200 |
WAF logs agree: requestBodySizeInspectedByWAF is 8192 on the ALB and 65536 on CloudFront KB_64, and larger bodies carry oversizeFields: ["REQUEST_BODY"].
Result 3: oversize rule in BLOCK (8,192 bytes)
Everything from 8,193 bytes is blocked regardless of position. So is a legitimate ~19 KB JSON POST with no rule string at all. An API that accepts JSON over 8 KB cannot use this as is.
Result 4: with WAF on both CloudFront and the ALB, the ALB blocks legitimate requests
CloudFront can inspect the first 64 KB of the body, so I raised CloudFront's Block-Oversize-Body threshold to 65,536 bytes. CloudFront then passes legitimate requests up to 64 KB and blocks bodies too large to inspect in full.
The ALB's Web ACL is where this goes wrong. Putting CloudFront in front of an ALB that already had WAF, or adding the same rules to both layers as defense in depth, is common. Even when the origin only accepts CloudFront, requests through CloudFront are evaluated again by the ALB Web ACL.
| ALB Block-Oversize-Body (8,192 bytes) | Legit 19 KB JSON | PROGRESS at byte 65,537 |
|-----------------------------------------|------------------|-------------------------|
| BLOCK | 403 alb:Oversize | 403 cloudfront:Oversize |
| COUNT | 200 | 403 cloudfront:Oversize |
(Both through CloudFront; CloudFront blocks above 65,536 bytes.)
At first I thought raising CloudFront's limit would be enough. With the ALB rule in BLOCK, though, the ALB's 8 KB rule stops the 19 KB JSON that CloudFront inspected and allowed. To benefit from CloudFront's setting, the ALB size rule has to be COUNT.
Result 5: restricting ALB access to CloudFront
COUNT on the ALB opens a different problem: a request sent straight to the ALB skips CloudFront, and the ALB inspects only 8 KB. A request with PROGRESS ending at byte 8,193, sent directly, was 403 with the ALB rule in BLOCK and 200 with it in COUNT.
If you follow the AWS best practice and restrict the ALB to traffic from CloudFront, the Result 4 fix (COUNT on the ALB) is not a problem. Setting it to COUNT while the ALB is still directly reachable, however, is dangerous.
As the documentation describes, I combined a security group and a custom header. The security group admits only the CloudFront origin-facing managed prefix list (com.amazonaws.global.cloudfront.origin-facing). That list is shared by every CloudFront distribution, so on its own it also lets other distributions in. I therefore set an X-Origin-Verify origin custom header on CloudFront, and the ALB listener rule forwards only requests whose header value matches (default action 403).
const cloudFrontPrefixList = ec2.PrefixList.fromLookup(this, 'CloudFrontOriginFacing', {
prefixListName: 'com.amazonaws.global.cloudfront.origin-facing',
});
albSecurityGroup.addIngressRule(ec2.Peer.prefixList(cloudFrontPrefixList.prefixListId), ec2.Port.tcp(80));
listener.addAction('FromCloudFront', {
priority: 10,
conditions: [elbv2.ListenerCondition.httpHeader('X-Origin-Verify', [originVerifyValue])],
action: elbv2.ListenerAction.forward([targetGroup]),
});
This approach relies on keeping the header name and value secret. For this verification CloudFront talks to the ALB over HTTP (port 80); the documentation recommends HTTPS and rotating the header regularly. Another option is to keep the ALB off the internet and connect an internal ALB to CloudFront as a VPC origin.
For Results 1–4, I had added an exception that let my own IP address reach the ALB directly, for comparison. After removing that exception and redeploying, a direct curl to the ALB got no response at all, not even a 403, and timed out: the security group drops the traffic before it reaches the ALB. Through CloudFront, requests still returned 200, and requests with the PHP2Shell pattern were still blocked with 403.
Other observations
| Check | Result |
|-------|--------|
| RateLimit-Posts (100 per 60 s, COUNT) at ~2.4 req/s | first match on request #138, 57 s in |
| 1,100,000-byte body, ALB → Lambda | 413 (1 MB Lambda target limit), not in the ALB WAF log |
| WCU | CloudFront 50 / ALB 45 (AWSManagedRulesCommonRuleSet alone is 700) |
| pm.max_requests=1 | FPM worker PID changes on every request |
| Lambda container cold start | 3.2 s first, 0.2 s after; ~48 MB memory |
The rate-based rule first matched on request #138 (in COUNT; in BLOCK, blocking would start there). The threshold is 100 per 60 seconds, so about 16 seconds and 38 requests went through after it was crossed. If you set the threshold to exactly the number you can tolerate, requests that arrive while detection lags go through on top of it. The overshoot is roughly "request rate × lag": here, 2.4 req/s × ~16 s ≈ 38. Faster attacks overshoot more, so set the threshold below what you can tolerate. The AWS WAF documentation also says rate-based rules are not meant for precise rate limiting, and that the detection lag is usually under 30 seconds but can stretch to minutes.
Cost
About $40/month if left running (ALB ~$18, two Web ACLs $10, eight rules $8, plus requests). With KB_64, only requests whose bodies exceed 16 KB pay an extra $0.30 per million per additional 16 KB. The verification itself, deploy to destroy within a few hours, cost under $1.
Wrap-up
The AWS Security Blog rules behave as specified on both CloudFront and the ALB, but only for strings within the inspected part of the body. Even with CloudFront's limit raised to 64 KB, an 8 KB rule left on the ALB blocks legitimate requests, and a directly reachable ALB lets requests through with everything past 8 KB uninspected. Configure WAF for the whole request path rather than for CloudFront and the ALB separately.
That said, every measure verified here is a mitigation. Upgrade PHP first.
References
- Using AWS WAF to apply mitigations for PHP2Shell RCE vulnerabilities (AWS Security Blog)
- PHP security advisory (2026-10-21)
- PHP security advisory GHSA-w8hw-w9fx-j79w (CVE-2026-109618)
- PHP security advisory GHSA-6wmf-4rv5-p3rp (CVE-2026-109619)
- CERT-EU Security Advisory 2026-018: Critical Vulnerabilities in PHP
- NCSC-NL php2shell operational information
- Amazon Threat Intelligence identifies PHP2Shell exploitation prior to public disclosure in MadPot
- AWS Security Bulletin 2026-143-AWS
- Classmethod: Trying the AWS WAF rules from the AWS Security Bulletin (2026-143-AWS) and the AWS Security Blog to mitigate PHP2Shell
- Restrict access to Application Load Balancers (CloudFront Developer Guide)
- AWS WAF: Considerations for managing body inspection
- AWS WAF: Rate-based rule caveats
- AWS WAF pricing
1 reaction
Mentions
PHP2Shell: How to Check Your Website for the Critical PHP Flaw If your website runs on PHP, which includes almost every WordPress site, there is one update to confirm with your host this week. On October 21, 2026, the PHP project released fixes for two serious flaws, CVE-2026-109618 and CVE-2026-109619, nicknamed PHP2Shell. Both can let an attacker run code on a server without logging in, and attacks have been reported. The good news is that the fix is a normal PHP update, and your host probably handles it. This post explains what happened and gives you a short list of things to ask and check. What happened PHP released new versions on October 21 that fix two related bugs in how the server handles web requests. In plain terms, the first one can leave a request half built when the server runs out of memory, and the clean-up code then trusts it. The second lets data from one request survive after its memory is freed, and a later request on the same worker can read it. In both cases the result can be remote code execution. Each scores 9.2 on the CVSS 4.0 scale. Security researchers at watchTowr said they saw exploitation from October 21, and CISA added CVE-2026-109618 to its list of known exploited vulnerabilities. Cloudflare has also reported attack traffic aimed at the flaws, and public proof-of-concept scripts exist. The software that serves PHP matters here: Apache with mod_php carries the highest risk, though PHP-FPM, LiteSpeed and others are also in scope. Which versions are fixed The fixed releases are: - PHP 8.5.12 - PHP 8.4.27 - PHP 8.3.36 - PHP 8.2.35 Anything newer than these is also fine. PHP 8.1 and older no longer get regular security updates from the PHP project. The project did publish limited source backports for 7.4, 8.0 and 8.1, and many hosts and Linux distributions ship their own patched packages. That means your version number can look old and still include the fix. Your host can confirm. One useful point from the WordPress side: this is a server issue, so updating WordPress or plugins does not fix it. The WordPress security team has said no WordPress update is needed. Check 1: Find your PHP version In WordPress, open Tools, then Site Health, then the Info tab, and look under Server. Otherwise, check your hosting control panel. Write the version down for every site you run, including old ones and test sites. Check 2: Ask your host three plain questions Ask which PHP version is running, whether it includes the fixes for the two CVEs, and when the fix was applied. Also ask whether PHP and the web server were restarted afterward. Running processes keep the old code in memory until they restart, so a patch without a restart is not finished. Check 3: Upgrade if you are on an old branch If you are on PHP 8.1 or older and your host has no patched package, plan an upgrade to a supported branch. PHP 8.3 or newer is a sensible target. PHP 8.2 reaches the end of its support on December 31, 2026, so many people are choosing to skip it. Test your plugins and theme on a staging copy first, since a jump of several versions can break older code. Check 4: Keep a temporary setting in place Upgrades take time, and the PHP project suggests a few interim steps. One is switching off session upload progress, with the setting session.upload_progress.enabled=0, then restarting the workers. It reduces exposure to one of the flaws. PHP is clear that no configuration change fully fixes these bugs, so treat it as a short-term measure. Check 5: Look for signs of trouble Early attempts may date back to around October 17. Look for admin users you do not recognize, unfamiliar files, new scheduled tasks, and unexplained crashes or restarts of your web server. If anything looks odd, change passwords and keys, starting with hosting and admin accounts, and turn on multi-factor authentication. Check 6: Be careful with offers of help Every big security story brings a wave of messages offering a quick fix. Do not install a "PHP2Shell fix" plugin or paste code from a forum or social post. Contact your host through its official website or phone number instead. A short checklist - PHP version noted for every site - Host confirms the fixed version or a backported package - PHP and web server restarted after the update - Plan in place if you are on PHP 8.1 or older - session.upload_progress.enabled set to 0 until you are patched - Admin users, files and scheduled tasks reviewed - Passwords changed and multi-factor authentication on Why this one is a quiet job Most of this work happens on your host's side, so the main job is asking and getting an answer in writing. If you run a business site, a short message to your hosting provider today is a good use of five minutes. For more practical website guides, visit expertwebdeveloper.in and growsmartwithai.com. Sources - Qualys: PHP2Shell: Find, Mitigate, and Patch Now - watchTowr: CVE-2026-109618: Exploited Remote Code Execution in PHP - U.S. Chamber of Commerce: A Security Flaw Is Affecting Millions of Websites: What Small Business Owners Need to Do - NCSC-NL: Operational information regarding the 2026 php2shell vulnerabilities Also published here - Medium - Substack - expertwebdeveloper.in - growsmartwithai.com - LinkedIn
PHP2Shell: What Happened, Why It Happened, and What to Do Now Originally published on ryanverwey.dev. The immediate answer The PHP security emergency unfolding on October 21, 2026 concerns PHP2Shell, two critical vulnerabilities in the PHP runtime: CVE-2026-109618 and CVE-2026-109619. Both can enable unauthenticated remote code execution under exploitable conditions. PHP has released fixes. If you operate a PHP website, verify the PHP runtime actually serving its requests and update it immediately. A WordPress, Drupal, or plugin update alone is not the same operation. PHP's release advisory For operators in a hurry: establish an owner for every PHP deployment, apply a verified fixed package or image, replace old running workers, and investigate suspicious activity independently of patching. If you cannot remediate an exposed application promptly, consider isolating it from public access. That isolation recommendation is also present in the European agencies' joint statement. This is a developing incident, not a completed forensic history. This article is a publication-day assessment dated October 21, 2026, Eastern Time. It separates maintainer-confirmed behavior, researchers' demonstrations, providers' observations, and my own engineering recommendations. It does not claim to have tested an exploit or investigated a victim organization. The diagrams are original explanatory illustrations, not screenshots of attacks or measurements of compromise. What actually happened, and what remains unproven Calling this “the PHP hack” compresses several different events into one phrase. Researchers established serious flaws in PHP. Maintainers coordinated emergency fixes. Exploit demonstrations and public attention accelerated. Providers began deploying defenses, while some operators temporarily withdrew services. These categories matter because each answers a different question: | Evidence category | What it establishes | What it does not establish | | --- | --- | --- | | A vulnerable version is installed | A remediation or vendor-validation task exists | That an attacker reached the installation | | A researcher demonstrates execution | A particular setup can be exploited | That every deployment is equally exploitable | | A security provider sees attack traffic | Attempts are reaching monitored infrastructure | That every attempt succeeded | | A service goes offline | Availability changed | Whether the cause was compromise or precaution | | Forensic evidence confirms attacker activity | A specific system was compromised | The total number of victims worldwide | Cloudflare says it observed attacks against its customers and changes to payloads intended to evade protection. It says its PHP2Shell protections became available by default for customers using its free or paid WAF at 03:02 UTC on October 21. Those are the provider's observations and protection claims, not a census of successful breaches. Cloudflare's incident report Wiz reports demonstrating unauthenticated code execution against the WordPress, Drupal, and Joomla container images it tested. It also describes memory disclosure and multiple variants grouped under the two CVEs. This is significant evidence of practical exploitability, but a demonstration against a named application's deployment does not mean that application's own code introduced the flaw. Wiz Research There are also documented precautionary outages. Email-alias service addy.io says it took its website and web application offline while updating, then restored them; email delivery continued. Its explanation describes a protective shutdown, not a confirmed intrusion. addy.io's account I could not establish a reliable global victim count from the primary sources reviewed. Claims that a percentage of “the internet was hacked” confuse technology adoption with confirmed compromise. We also should not infer a particular criminal group, motive, or data-theft campaign without evidence tying it to an intrusion. The disclosure timeline The PHP Foundation traces early signs to 2024 audit follow-up, before a concrete execution path was established. It attributes delays to resource constraints and difficult compatibility decisions. Reports from Calif, OpenAI, Wiz, and others later clarified the impact. PHP Foundation retrospective | Date | Documented development | | --- | --- | | 2024 | Early signs of the OOM problem emerge during audit follow-up. | | August 2026 | Concrete reports clarify the impact and intensify remediation. | | October 20 | CVE identifiers circulate; PHP issues advance notice. | | October 21 | Emergency releases and technical advisories become public. | The Foundation describes leaked identifiers, an accidentally published demonstration, and premature distributor images. The planned October 22 release moved forward. This article reports that account without independently adjudicating participants' responsibility. Foundation disclosure account !Illustrated sequence: early signs in 2024, concrete reports in August 2026, disrupted disclosure on October 20, and fixes released October 21. Figure 1. A deliberately coarse timeline. Sources: the Foundation account above and the release notices below. It shows sequence, not the duration of confirmed malicious activity. The pre-announcement scheduled release for 14:00 UTC, or 10:00 a.m. EDT. Publication-day summaries differ on the precise earlier availability time: CERT-EU records 13:30 UTC; ENISA records 13:40 UTC. The defensible statement is that fixes became available October 21, before the announced deadline, rather than presenting one minute as universally settled. PHP advance notice, CERT-EU, ENISA Why these vulnerabilities are unusually consequential PHP runs on the server. It processes requests and executes application code before sending a response to a browser. A problem in that runtime can therefore sit underneath many unrelated products. The UK NCSC explicitly tells organizations to inventory PHP throughout their infrastructure, including appliances and third-party services that bundle it. NCSC alert The important architectural question is where the security boundary sits. An application login protects application actions after the application can enforce that login. It cannot reliably protect a vulnerable runtime operation that happens earlier. Likewise, a protection implemented inside PHP depends on PHP safely reaching that protection. This is why I would challenge the reassuring statement “our admin page requires multifactor authentication.” MFA remains valuable, but it answers a different question. The relevant question here is whether untrusted network input can reach vulnerable PHP processing at all. CVE-2026-109618: interruption during partially initialized state PHP's OOM advisory explains that longjmp() can abort processing after an allocation failure while internal request data is only partly initialized. Cleanup can then consume invalid state. It documents unauthenticated remote execution on Apache mod_php, including before application code runs. The fix changes OOM handling to defer a soft failure to a safe execution point, with a hard exit at a higher threshold. Maintainer OOM advisory A useful conceptual model is an interrupted handoff: one part of the engine has made a structure reachable, but has not finished making it safe for another part to use. Cleanup assumes the handoff completed. This analogy explains the lifetime problem; it is not a description of a payload. CVE-2026-109619: a later request uses yesterday's pointer The second advisory describes request-owned memory that is freed while a reusable worker retains references to it. An operation in a later request can consume that stale state before normal initialization replaces it. Exploitation requires a suitable producer and consumer, with requests reaching the same worker. The advisory confirms remote execution through session-lifecycle paths on both mod_php and PHP-FPM. Maintainer stale-state advisory Think of a reusable worker as a desk used for successive jobs. Clearing the old paperwork is not enough if the desk still contains instructions to use an old location. That location may now contain something else. The mistake is not simply “too much traffic”; it is treating an invalid reference as trustworthy state. !Two broken handoffs shown with layered blue structures: interrupted initialization reaches cleanup; freed memory leaves a stale reference for a later request. Figure 2. Simplified models of the two maintainer advisories. The sculptural forms illustrate lifecycle stages, not a runnable exploit or a complete list of variants. Configuration differences affect the path, not the patch obligation The PHP project explicitly includes PHP-FPM, mod_php, CGI, FrankenPHP, and LiteSpeed in its warning. It assigns the highest risk to mod_php. PHP release advisory There is a publication-day documentation inconsistency worth preserving: the OOM advisory says a complete FPM exploit was not demonstrated for that SAPI, while the Foundation's narrative says OOM-related FPM execution was confirmed through application paths. The separate stale-state advisory clearly confirms an FPM session-lifecycle exploit. FPM must not be treated as safe. Exact demonstration claims should name their CVE and source. OOM advisory, Foundation account, stale-state advisory Who is affected, and which versions contain fixes? Both issues are rated CVSS v4.0 9.2, Critical. A severity score is not a probability that your server has been compromised. The supported-branch remediation thresholds published for this incident are: CERT-EU advisory | PHP branch | First upstream release with these fixes | | --- | --- | | 8.2 | 8.2.35 | | 8.3 | 8.3.36 | | 8.4 | 8.4.27 | | 8.5 | 8.5.12 | Earlier builds in each branch require remediation or a verified vendor backport. !A joined blue slab displays the fixed releases: 8.2.35, 8.3.36, 8.4.27, and 8.5.12. Older branches require a verified vendor fix or migration. Figure 3. Publication-day patch reference. Source: CERT-EU. These are minimum fixed releases for this incident, not a recommendation to avoid later security updates. PHP also supplied minimal source backports for 7.4, 8.0, and 8.1. That exceptional assistance does not restore normal upstream support. Distribution and commercial support packages may use their own revision numbers, so compare the complete package revision with the vendor's advisory rather than rejecting or accepting it solely from the upstream-looking number. PHP's update instructions PHP's published lifecycle gives branches two years of active support followed by two years of security support. An emergency fix is therefore a reason to review the next upgrade deadline, not to reset your support assumptions. PHP support policy Wiz traces affected code as far back as PHP 5.4, while describing different requirements across variants. Treat legacy systems as an inventory priority; do not interpret “our version is old” as immunity. Wiz technical research For embedded products, consult the vendor and the NCSC-NL coordination repository. A missing product entry is not evidence of safety. Record the vendor's actual determination, its date, and the affected product version. Our take: could this have been prevented? The honest answer has three parts. Runtime design and security governance could have reduced the chance or duration of the vulnerability. Deployment controls could have reduced exposure. Containment could have reduced damage after exploitation. None supports a claim that an individual administrator could have patched an unavailable release. The following is my engineering analysis, not a claim that these measures were tested against every PHP2Shell variant. What AI changes, and what it does not Wiz describes frontier AI as helping researchers turn difficult, longstanding engine flaws into practical demonstrations. That is a claim about the research process; it does not establish that these defects were originally written by AI or that every malicious attempt is AI-operated. Wiz's research account My interpretation is that faster discovery increases the value of a fast, reliable response system. A tool that finds more defects does not automatically supply the maintainers, compatibility decisions, distribution coordination, or deployment capacity needed to remove them. Defenders should measure time to a verified fix, not celebrate report volume as though it were the same outcome. Nor should “AI found it” become a reason to skip validation. A security report still needs reproducibility, a clear affected configuration, an impact assessment, and a reviewed correction. The useful investment pairs better discovery with the human and operational capacity to act on credible findings. 1. Treat exceptional control flow as part of the security model Most application tests ask whether valid input produces the correct output. Runtime tests also need to ask what remains true when execution stops at the worst possible point. Allocation failure, timeout, error callbacks, shutdown hooks, and worker reuse deserve explicit attention. For systems with manual memory management, I would make ownership and initialization invariants reviewable: who owns a structure, when it becomes visible, which exit paths can interrupt its construction, and who may still reference it after teardown. Fault injection should deliberately fail allocations across those transitions. Sanitizers and fuzzing can help reveal violations, but passing a test campaign cannot prove that all memory-safety defects are absent. A useful review exercise is to walk each lifecycle backward. If cleanup runs now, what does it assume has already happened? If a second request starts now, what state does it inherit? That method targets the kind of boundary error illustrated here without needing a weaponized reproduction. 2. Give difficult security decisions an escalation deadline When every proposed fix has compatibility or availability costs, consensus can become a waiting room. My recommendation is to assign one accountable decision owner, require written tradeoffs, set a deadline for escalation, and reserve engineering capacity for high-impact unresolved risks. The goal is not to pretend that maintainers can fix everything instantly. It is to prevent “we cannot agree on the least harmful change” from silently becoming a long-term security policy. An organization that depends on an open-source runtime should fund maintenance and review capacity as part of its operating cost. 3. Make emergency rollout a rehearsed product capability A team should be able to answer where PHP runs, who can update it, how a fixed build enters production, how old workers are retired, and what proves completion. Those answers should exist before a headline arrives. I would rehearse a runtime update using a small representative deployment, then expand with observable health checks. The rollback plan must be explicit: reverting to a known-vulnerable runtime may restore availability while reopening the security exposure. A better fallback may be isolation or a reduced service built on a verified fixed image. 4. Reduce what the PHP process can reach Successful code execution has the privileges and access of the compromised process; it is not automatically root access to every connected system. That distinction creates a concrete design opportunity. Use narrowly scoped database accounts, separate service identities, limited filesystem write access, and restricted network paths. For example, a public content site should not need a database credential that can administer every tenant. A web container should not need a mounted container-engine socket. A content upload directory should not become an unrestricted program-execution location. These are examples of reducing consequences, not assertions about what happened to a particular victim. !A PHP worker connects through access boundaries to files, credentials, and a network. Limiting permitted access can reduce potential impact. Figure 4. Original defensive threat model. Orange paths show possible access if a process is compromised. They do not assert lateral movement, root access, or data theft in this incident. What to do if you think you are impacted Run two tracks together: remove the vulnerability and determine whether it was used. Do not make one the completion criterion for the other. Step 1: identify the real runtime and the accountable operator Start with public websites, but include internal tools, staging sites, old subdomains, disaster-recovery instances, containers, and appliances. For each deployment, record its hostname, environment, owner, web-server integration, PHP version, package revision or image digest, and exposure path. The NCSC's guidance includes both infrastructure inventory and monitoring vendors for bundled-PHP updates. NCSC alert With authorized shell access, these read-only commands provide a starting point: php -v php --ini They report the invoked command-line binary and its configuration. They do not prove that Apache, an FPM pool, another container, or every node behind a load balancer is using that binary. PHP documents both switches in its command-line reference. Its configuration documentation also explains that configuration loading depends on how PHP runs. Use authenticated administration or deployment inspection to establish the web runtime. Avoid leaving a public diagnostic page online: phpinfo() can reveal configuration, paths, headers, and environment information. PHP's phpinfo documentation On managed hosting, ask for a precise answer: “Which package revision fixes both CVEs on my account, when was it deployed, and have all serving workers been replaced?” A generic message that the provider is monitoring the situation is not evidence that your instance is patched. WordPress explicitly tells site owners to check with their host and update PHP. WordPress security notice Step 2: patch what is running, not just what is stored My recommended completion record contains the old revision, the fixed revision, the deployment time, the worker replacement time, and the verification result. Keep one record per independently deployed service rather than a single checkbox for the whole estate. For containers, rebuild or select a verified fixed image and redeploy the application. Updating the host's PHP package does not establish what is inside an image. Cloud.gov's incident instructions specifically distinguish its PHP buildpack from customer-managed container images and tell the latter group to update and redeploy. Cloud.gov guidance For long-running workers, verify replacement rather than assuming that a package installer did it. PHP's configuration-loading behavior explains why editing a file alone is insufficient for server modules. PHP configuration lifecycle Then check login, sessions, uploads, scheduled processing, and the application's main business transaction. These are practical smoke checks, not exploit tests. Verify every replica and the images used for future scale-out, or the next restart may reintroduce an old build. Step 3: if patching is blocked, contain exposure deliberately Prefer restricting access before requests reach PHP. A maintenance page rendered by the vulnerable application is a poor isolation boundary. Use the load balancer, reverse proxy, network controls, or an independently served static page, and account for alternate hostnames and direct-origin access. The official configuration workarounds are partial. In abbreviated form, they include administratively enforcing memory_limit=-1 for the OOM path, disabling session upload progress before parsing, and disabling fatal_error_backtraces on PHP 8.5 for a specific stale-state path. Workers must be restarted. The advisory warns that underlying allocation failures and other variants remain possible; RLIMIT_AS and RLIMIT_DATA are inappropriate substitutes. CERT-EU mitigation details Do not turn those settings into an unattended “fix everything” script. Removing PHP's normal memory ceiling changes availability risk. Request limits must be evaluated against real application behavior and the frontend's handling of decoded data. Use the linked vendor instructions for the full conditions, and assign an expiry and patch owner to temporary measures. A WAF can buy time, but it is a separate control. Cloudflare itself says customers should still patch. Confirm that the applicable rules are enabled and that requests cannot bypass the protected entry point. Cloudflare guidance Step 4: investigate evidence of exploitation Begin by preserving the information you will need to reconstruct events. That can include proxy and web-server logs, PHP errors, host process telemetry, authentication events, outbound connections, deployment history, and relevant cloud audit records. Record collection time and timezone, protect the originals, and restrict access to evidence that contains sensitive data. Elastic's PHP2Shell detection discussion emphasizes behavior such as unexpected processes launched by web services and suspicious follow-on execution. Those are useful investigative signals, but a detection rule needs the appropriate telemetry and can have blind spots. Elastic Security Labs My recommended investigation questions are: - Did a PHP or web-server worker start an unexpected shell, interpreter, or downloader? - Did executable files, configuration, scheduled tasks, or administrator accounts change outside a known deployment? - Did the service make unusual outbound connections or access secrets it normally leaves untouched? - Do memory errors, worker crashes, or unusual requests correlate with those stronger signals? - What evidence is missing because logging was disabled, incomplete, or already rotated? A memory error by itself is not proof of intrusion. A suspicious request by itself is not proof of successful execution. Conversely, a quiet access log does not prove no compromise: it may not contain request bodies, process activity, or evidence from an alternate route. Step 5: if compromise is suspected, recover trust Escalate to the incident-response owner or a qualified responder. Isolate affected systems while balancing evidence preservation and ongoing harm. Avoid casually wiping or rebooting a suspected host before deciding whether volatile evidence is needed; do not leave an active intrusion connected merely to collect more logs. For confirmed compromise, my recommendation is to rebuild from trusted sources after evidence collection, fix the entry path, and evaluate every credential accessible to the affected service. Rotate or revoke exposed secrets from a clean administrative environment. Consider database credentials, API tokens, service identities, signing keys, and sessions according to what the investigation establishes. Patching a runtime does not remove an attacker's separate persistence or invalidate a copied secret. This workflow follows the broad separation of detection, response, and recovery in NIST SP 800-61 Revision 3; the PHP-specific operational checklist here is my synthesis. Decisions about customer notification, contractual duties, or regulatory reporting should go through the organization's incident and legal teams using the actual facts and applicable jurisdiction. !Two separate response tracks: remediate through patching, restarting, and verification; investigate through preserving evidence, assessment, and recovery. Figure 5. An original response checklist. Remediation and investigation proceed in parallel; a completed update is not a forensic clearance. What this means for website owners and ordinary visitors If you own a website but someone else manages its hosting, forward the two CVE identifiers and request instance-specific confirmation. Ask whether their response covers the origin, all PHP versions assigned to your account, and any staging or secondary sites. If they cannot provide a safe update path, discuss temporary isolation and a supported migration rather than relying on the fact that the homepage still works. If you only visit a website, you cannot patch its server from your browser. An outage or maintenance banner does not establish that your personal information was stolen. Follow the operator's official security communications and any account-specific instructions. Do not install a “PHP repair” utility offered by a random message or submit credentials to an unsolicited emergency-checking page. If an operator confirms credential or session exposure, follow its reset instructions through the legitimate site and address reused passwords on other services. This is contingent advice for confirmed or credibly suspected exposure, not a claim that every visitor to a PHP site now needs an emergency reset. The lesson I would carry into the next incident PHP2Shell makes a familiar operational weakness visible: a team may know its application very well while having a poor account of the runtime underneath it. The useful outcome is not simply a newer version number. It is a defensible chain of evidence from advisory, to affected asset, to fixed deployment, to verified running service, with a separate answer about possible intrusion. My standard would be straightforward: name the owner, prove the revision, verify the workers, preserve the evidence, and state what remains unknown. That is a much stronger response than either minimizing a serious flaw or describing every vulnerable site as already breached. For more context on how I approach implementation and operational tradeoffs, see my development background and the projects behind this portfolio. Sources and reporting method Primary sources were preferred over social posts and secondary headlines. Source statements were checked on October 21, 2026, Eastern Time. The list below is a reading trail; citations beside individual claims identify their basis. Source pages can change after publication. Differences in release timing and FPM demonstration language are explicitly retained above rather than silently reconciled. 1. PHP Security Team. (2026, October 21). Critical remote code execution vulnerabilities: update now. Release scope, fixed versions, and update guidance. 2. PHP maintainers. (2026, October 21). RCE via OOM corruption, GHSA-w8hw-w9fx-j79w. CVE-2026-109618 technical advisory. 3. PHP maintainers. (2026, October 21). RCE via stale global state, GHSA-6wmf-4rv5-p3rp. CVE-2026-109619 technical advisory. 4. The PHP Foundation. (2026, October 21). Today's PHP Security Release, and How We're Strengthening PHP Security. Maintainer-side history and disclosure account. 5. PHP Security Team. (2026, October 20). Upcoming Critical PHP Security Release. Advance notice. 6. CERT-EU. (2026, October 21). Security Advisory 2026-018. Severity, versions, and mitigation conditions. 7. ENISA and partner agencies. (2026, October 21). Joint statement on critical PHP vulnerabilities. Coordinated operational advice. 8. UK National Cyber Security Centre. (2026, October 21). Critical security issue affecting PHP. Inventory, monitoring, and isolation guidance. 9. Tzadik, S., Tamari, S., Ohfeld, N., & Schwartz, T. (2026, October 21). PHP2Shell technical research. Wiz's discoveries and demonstrations. 10. Molteni, D., Jones, D., & Nandwani, K. (2026, October 21). Cloudflare deploys protections against php2shell. Provider observations and WAF claims. 11. addy.io. (2026, October 21). Why addy.io went offline. First-party account of a precautionary outage. 12. Blackbourn, J. (2026, October 21). Critical PHP Security Release. Guidance for WordPress site owners. 13. Cloud.gov. (2026, October 21). Action required: Critical PHP vulnerabilities. Buildpack and container remediation responsibilities. 14. Elastic Security Labs. (2026, October 21). Assessing detection coverage for PHP2Shell. Behavioral detection considerations. 15. NCSC-NL. (2026). PHP2Shell coordination repository. Evolving third-party product information. 16. PHP documentation. Supported versions, command-line options, configuration loading, and phpinfo. Runtime-verification background. 17. Nelson, A., Rekhi, S., Souppaya, M., & Scarfone, K. (2025). NIST SP 800-61 Revision 3. General incident-response framework, not a PHP2Shell advisory.
php2shell、製品ベースの統計だと今日時点でまだ2割ぐらいしか完了してないのか。こりゃマズイね。 NCSC-NL/php2shell t.co
Le Nationaal Cber Security Centrum (NCSC-NL) consolident dans un dépôt Github des infos sur les mises à jour de produits embarquant PHP. (Source ANSSI). NCSC-NL/php2shell
PHP2Shell - high-impact computer vulnerabilities concerning memory safety flaws that allow remote code execution (RCE) by a potential malicious user to gain remote control of the website host. wiki- en.wikipedia.org github - NCSC-NL/php2shell t.co
Топ-9 GitHub за 3 дня: reylo и 3d-model-anything Обзор: топ-9 новых репозиториев GitHub за 20–23 октября 2026 — новые проекты GitHub и open source, которые быстрее всех набрали звёзды. В выпуске: типизированный импорт .sql в TypeScript, скан комнаты на iPhone в сцену Three.js и сканер уязвимости PHP. 1. colinhacks/reylo — Импорт .sql-файлов в TypeScript с типами из схемы БД, без кодогенерации. (Rust, +753 ★) 2. wesbos/3d-model-anything — iPhone-приложение и навык для агента: скан комнаты → сцена Three.js. (Swift, +606 ★) 3. assetnote/php2shell-scanner — Консольный сканер уязвимостей CVE-2026-109618 и CVE-2026-109619 в PHP. (Python, +343 ★) 4. zita-ch/supermimic_vibed — Неофициальное воспроизведение SuperMimic: управление гуманоидом G1 в Isaac Lab 3. (Python, +327 ★) 5. rossark0/rossark-motion-skill — Навык для Claude Code: оживляет нарисованное — персонажей, сцены, иконки. (Swift, +322 ★) 6. raymondchins/Bridgeplay — Windows-игры из Steam на Mac с Apple Silicon, установка в один клик. (Scala, +272 ★) 7. NCSC-NL/php2shell — Сводка NCSC-NL по уязвимостям PHP CVE-2026-109618 и CVE-2026-109619. (Python, +236 ★) 8. helloianneo/ian-xhs-carousel — Навык Claude Code: статья или заметка → серия картинок 3:4 для Xiaohongshu. (HTML, +232 ★) 9. qianshuixia/shrimpreel — Шаблон сайта «одно фото → ИИ-видео по вирусному шаблону» на Cloudflare. (TypeScript, +219 ★) Источник: GitHub Search API, репозитории созданы с 2026-10-20, отсортированы по звёздам. Лидер: colinhacks/reylo Остальные: wesbos/3d-model-anything, assetnote/php2shell-scanner, zita-ch/supermimic_vibed, rossark0/rossark-motion-skill, raymondchins/Bridgeplay, NCSC-NL/php2shell, helloianneo/ian-xhs-carousel, qianshuixia/shrimpreel #GitHub #opensource #программирование Каждые 3 дня — 10 новых open-source проектов GitHub, быстрее всех набравших звёзды. Подписывайся, чтобы не пропустить следующий выпуск.
If anyone needs a growing list of vendors affected by php2shell. The Netherlands National Cybersecurity Center is so kind to publish their list on GitHub. You can ctrl +f the .md list on Github. github.com/NCSC-NL/php2shell/blob/main/software/c…
No, sorry. Full stop. The php vulnerabilities are real. GitHub is nonaligned, & run by geeks who know their stuff. php2shell vulnerabilities (CVE-2026-109618, CVE-2026-109619) NCSC-NL/php2shell
Testing the AWS WAF Mitigations for PHP2Shell on Both CloudFront and an ALB --- title: Testing the AWS WAF Mitigations for PHP2Shell on Both CloudFront and an ALB published: true description: 'I deployed the AWS WAF rules published for PHP2Shell (CVE-2026-109618 / 109619) on CloudFront (64 KB body inspection) and on an ALB (fixed 8 KB) with AWS CDK and measured inspection boundaries, false positives, per-layer thresholds and origin bypass.' tags: 'aws, security, cdk, php' cover_image: null organization_id: 2794 id: 4897722 date: '2026-10-22T16:10:45Z' --- Introduction On 2026-10-21 the PHP project disclosed PHP2Shell (CVE-2026-109618 / CVE-2026-109619): CVSS v4.0 9.2, unauthenticated remote code execution (PHP security advisory, CERT-EU Security Advisory 2026-018). As a stopgap until PHP can be upgraded, an AWS Security Blog post published four AWS WAF custom rules: PHP2Shell, Block-Multipart-FormData, Block-Oversize-Body and RateLimit-Posts. The AWS Security Bulletin also points to SizeRestrictions_BODY in AWSManagedRulesCommonRuleSet and to the PHP managed rule group (version 2.3, now the default), which adds protection for CVE-2026-109619. Classmethod has already tested the AWS Security Blog rules thoroughly on an ALB, including the fact that PHP_SESSION_UPLOAD_PROGRESS placed after the first 8 KB of the body goes through. This article reproduces that test on both CloudFront and an ALB and verifies what was still open: - How far does raising CloudFront's body inspection limit (16 KB default, 64 KB max) get you? - How does the ALB's Web ACL behind CloudFront affect the result? - What changes when the origin only accepts traffic through CloudFront? - How long does the rate-based rule take to react once the threshold is crossed? 📁 Code: waf-php2shell-mitigation (part of AWS CDK Reference Architectures). > Note > AWS WAF is a mitigation, not a fix. Upgrading to PHP 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12 or later is the only complete remediation. PHP 8.1 and older are also affected but are end-of-life, so there is no official fixed release (the PHP team published minimal source backports, and some distributions and LTS vendors provide backported packages). Move to a supported version. TL;DR The AWS Security Blog rules are a stopgap until PHP is upgraded; the AWS Security Blog post itself states that the real fix is upgrading PHP. As a stopgap, the rules behaved as specified on both CloudFront and the ALB. AWS WAF, however, inspects a request body only from the start up to a size limit: 8 KB on an ALB (fixed), 16 KB by default on CloudFront, up to 64 KB (AWS WAF documentation). On the ALB, a string ending at byte 8,192 was blocked; shifting it by one byte let it through. The Block-Oversize-Body rule from the same post (a size limit meant for CVE-2026-109618), when set to BLOCK, also stops requests whose bodies go past that limit. How to configure that rule depends on your setup: - If you use CloudFront, raise the inspection limit to 64 KB and block bodies larger than 64 KB with Block-Oversize-Body. The whole body is then inspected, and legitimate requests up to 64 KB still pass. - With CloudFront + ALB + WAF, set the ALB's "block bodies over 8 KB" rule to COUNT and let the ALB accept traffic from CloudFront only. Left in BLOCK, the ALB stops legitimate requests that CloudFront already allowed. If the ALB Web ACL includes AWSManagedRulesCommonRuleSet, override its SizeRestrictions_BODY (blocks bodies over 8 KB) to COUNT for the same reason. - If you expose only an ALB, blocking bodies over 8 KB is the baseline. According to the AWS Security Blog post, both CVEs sit on the multipart/form-data processing path, so an app that takes no file uploads and only accepts JSON over 8 KB could block Block-Multipart-FormData instead of using the size limit (not verified in this article). An app that accepts file uploads over 8 KB cannot be fully protected by WAF, so prioritize the PHP upgrade. If your app changes session.name or session.upload_progress.name from the defaults, rewrite the rule strings to the actual names. Matching is case-sensitive, so this also applies when only the letter case differs. Architecture !Architecture Client ─▶ CloudFront + Web ACL (us-east-1, 64 KB body inspection) ─▶ ALB + Web ACL (ap-northeast-1, fixed 8 KB) ─▶ Lambda container (nginx + PHP-FPM 8.4.27 + Lambda Web Adapter) Client ─────────▶ ALB direct (comparison only, allow-listed IP) A WAF decision does not depend on the PHP version behind it, so the origin runs a patched PHP 8.4.27 as a Lambda container image. On the PHP side, it applies these server-side mitigations from the AWS Security Blog post: ; php.ini session.upload_progress.enabled = 0 memory_limit = -1 ; the real ceiling is the Lambda memory size post_max_size = 8M ; PHP-FPM pool pm = static pm.max_children = 1 pm.max_requests = 1 ; replace the worker after every request Telling which WAF and which rule acted, from HTTP headers With WAF on both CloudFront and the ALB, a bare 403 does not tell you which layer answered. Every rule therefore names itself: const ruleAction = (ruleName: string, action: RuleAction): wafv2.CfnWebACL.RuleActionProperty => action === 'BLOCK' ? { block: { customResponse: { responseCode: 403, responseHeaders: [{ name: 'x-php2shell-blocked-by', value: `${label}:${ruleName}` }] } } } : { count: { customRequestHandling: { insertHeaders: [{ name: `php2shell-${label}-${ruleName.toLowerCase()}`, value: 'counted' }] } } }; BLOCK returns x-php2shell-blocked-by: cloudfront:PHP2Shell; COUNT inserts x-amzn-waf-php2shell-alb-oversize: counted into the origin request, and the PHP app echoes it back. Headers inserted by the CloudFront Web ACL travel through the ALB to the origin, and the BLOCK response header keeps its own name (no x-amzn-waf- prefix). Note that clients can also send headers starting with x-amzn-waf-. If your application changes its behavior based on these headers in production, add a rule that blocks x-amzn-waf- headers arriving from outside, as the AWS WAF documentation describes. Result 1: rule conditions (reproducing the ALB test on both layers) | Request | via CloudFront | ALB direct | |---------|----------------|------------| | POST, cookie PHPSESSID + PHP_SESSION_UPLOAD_PROGRESS in body | 403 | 403 | | POST, both strings in body | 403 | 403 | | POST, PROGRESS only / cookie only | 200 | 200 | | PUT, both strings in body | 200 | 200 | | multipart PROGRESS field + cookie | 403 | 403 | | lowercase cookie key phpsessid | 200 | 200 | | lowercase php_session_upload_progress in body | 200 | 200 | | JSON or text/plain containing both strings (false positive) | 403 | 403 | The rule uses TextTransformation: NONE, so it only matches PHP's default names. If your app renames the session cookie or the upload progress field, change the rule strings too. Result 2: string position vs inspection limit Cookie PHPSESSID, with PHP_SESSION_UPLOAD_PROGRESS ending at byte N (oversize rule in COUNT): | N | ALB direct | CloudFront KB_16 | CloudFront KB_64 | |---|-----------|--------------------|--------------------| | 8,192 | 403 | 403 | 403 | | 8,193 | 200 | 403 | 403 | | 16,385 | 200 | 200 | 403 | | 65,536 | 200 | 200 | 403 | | 65,537 | 200 | 200 | 200 | WAF logs agree: requestBodySizeInspectedByWAF is 8192 on the ALB and 65536 on CloudFront KB_64, and larger bodies carry oversizeFields: ["REQUEST_BODY"]. Result 3: oversize rule in BLOCK (8,192 bytes) Everything from 8,193 bytes is blocked regardless of position. So is a legitimate ~19 KB JSON POST with no rule string at all. An API that accepts JSON over 8 KB cannot use this as is. Result 4: with WAF on both CloudFront and the ALB, the ALB blocks legitimate requests CloudFront can inspect the first 64 KB of the body, so I raised CloudFront's Block-Oversize-Body threshold to 65,536 bytes. CloudFront then passes legitimate requests up to 64 KB and blocks bodies too large to inspect in full. The ALB's Web ACL is where this goes wrong. Putting CloudFront in front of an ALB that already had WAF, or adding the same rules to both layers as defense in depth, is common. Even when the origin only accepts CloudFront, requests through CloudFront are evaluated again by the ALB Web ACL. | ALB Block-Oversize-Body (8,192 bytes) | Legit 19 KB JSON | PROGRESS at byte 65,537 | |-----------------------------------------|------------------|-------------------------| | BLOCK | 403 alb:Oversize | 403 cloudfront:Oversize | | COUNT | 200 | 403 cloudfront:Oversize | (Both through CloudFront; CloudFront blocks above 65,536 bytes.) At first I thought raising CloudFront's limit would be enough. With the ALB rule in BLOCK, though, the ALB's 8 KB rule stops the 19 KB JSON that CloudFront inspected and allowed. To benefit from CloudFront's setting, the ALB size rule has to be COUNT. Result 5: restricting ALB access to CloudFront COUNT on the ALB opens a different problem: a request sent straight to the ALB skips CloudFront, and the ALB inspects only 8 KB. A request with PROGRESS ending at byte 8,193, sent directly, was 403 with the ALB rule in BLOCK and 200 with it in COUNT. If you follow the AWS best practice and restrict the ALB to traffic from CloudFront, the Result 4 fix (COUNT on the ALB) is not a problem. Setting it to COUNT while the ALB is still directly reachable, however, is dangerous. As the documentation describes, I combined a security group and a custom header. The security group admits only the CloudFront origin-facing managed prefix list (com.amazonaws.global.cloudfront.origin-facing). That list is shared by every CloudFront distribution, so on its own it also lets other distributions in. I therefore set an X-Origin-Verify origin custom header on CloudFront, and the ALB listener rule forwards only requests whose header value matches (default action 403). const cloudFrontPrefixList = ec2.PrefixList.fromLookup(this, 'CloudFrontOriginFacing', { prefixListName: 'com.amazonaws.global.cloudfront.origin-facing', }); albSecurityGroup.addIngressRule(ec2.Peer.prefixList(cloudFrontPrefixList.prefixListId), ec2.Port.tcp(80)); listener.addAction('FromCloudFront', { priority: 10, conditions: [elbv2.ListenerCondition.httpHeader('X-Origin-Verify', [originVerifyValue])], action: elbv2.ListenerAction.forward([targetGroup]), }); This approach relies on keeping the header name and value secret. For this verification CloudFront talks to the ALB over HTTP (port 80); the documentation recommends HTTPS and rotating the header regularly. Another option is to keep the ALB off the internet and connect an internal ALB to CloudFront as a VPC origin. For Results 1–4, I had added an exception that let my own IP address reach the ALB directly, for comparison. After removing that exception and redeploying, a direct curl to the ALB got no response at all, not even a 403, and timed out: the security group drops the traffic before it reaches the ALB. Through CloudFront, requests still returned 200, and requests with the PHP2Shell pattern were still blocked with 403. Other observations | Check | Result | |-------|--------| | RateLimit-Posts (100 per 60 s, COUNT) at ~2.4 req/s | first match on request #138, 57 s in | | 1,100,000-byte body, ALB → Lambda | 413 (1 MB Lambda target limit), not in the ALB WAF log | | WCU | CloudFront 50 / ALB 45 (AWSManagedRulesCommonRuleSet alone is 700) | | pm.max_requests=1 | FPM worker PID changes on every request | | Lambda container cold start | 3.2 s first, 0.2 s after; ~48 MB memory | The rate-based rule first matched on request #138 (in COUNT; in BLOCK, blocking would start there). The threshold is 100 per 60 seconds, so about 16 seconds and 38 requests went through after it was crossed. If you set the threshold to exactly the number you can tolerate, requests that arrive while detection lags go through on top of it. The overshoot is roughly "request rate × lag": here, 2.4 req/s × ~16 s ≈ 38. Faster attacks overshoot more, so set the threshold below what you can tolerate. The AWS WAF documentation also says rate-based rules are not meant for precise rate limiting, and that the detection lag is usually under 30 seconds but can stretch to minutes. Cost About $40/month if left running (ALB ~$18, two Web ACLs $10, eight rules $8, plus requests). With KB_64, only requests whose bodies exceed 16 KB pay an extra $0.30 per million per additional 16 KB. The verification itself, deploy to destroy within a few hours, cost under $1. Wrap-up The AWS Security Blog rules behave as specified on both CloudFront and the ALB, but only for strings within the inspected part of the body. Even with CloudFront's limit raised to 64 KB, an 8 KB rule left on the ALB blocks legitimate requests, and a directly reachable ALB lets requests through with everything past 8 KB uninspected. Configure WAF for the whole request path rather than for CloudFront and the ALB separately. That said, every measure verified here is a mitigation. Upgrade PHP first. References - Using AWS WAF to apply mitigations for PHP2Shell RCE vulnerabilities (AWS Security Blog) - PHP security advisory (2026-10-21) - PHP security advisory GHSA-w8hw-w9fx-j79w (CVE-2026-109618) - PHP security advisory GHSA-6wmf-4rv5-p3rp (CVE-2026-109619) - CERT-EU Security Advisory 2026-018: Critical Vulnerabilities in PHP - NCSC-NL php2shell operational information - Amazon Threat Intelligence identifies PHP2Shell exploitation prior to public disclosure in MadPot - AWS Security Bulletin 2026-143-AWS - Classmethod: Trying the AWS WAF rules from the AWS Security Bulletin (2026-143-AWS) and the AWS Security Blog to mitigate PHP2Shell - Restrict access to Application Load Balancers (CloudFront Developer Guide) - AWS WAF: Considerations for managing body inspection - AWS WAF: Rate-based rule caveats - AWS WAF pricing
A common question regarding #php2shell (CVE-2026-109618 & CVE-2026-109619) in #php is; "what's vulnerable?" The awesome people at NCSC-NL have provided the answer. If it's on this list, it's vulnerable. (It's a very long list in alphabetical order) NCSC-NL/php2shell