Safeguard
Supply Chain Security•

PHP2Shell WAF rules: what they block and where they fall short

WAF and CDN rules for PHP2Shell buy hours, not safety. They miss direct-to-origin traffic and new variants. Patch PHP, restart workers, then verify.

Safeguard Security Team
24 min read

WAF and CDN rules for PHP2Shell (CVE-2026-109618 and CVE-2026-109619) block known exploit patterns on traffic that passes through the provider. They do not fix PHP, they miss requests that reach your origin directly, and they can lag new variants. Treat them as a stopgap: lock down the origin, patch PHP, restart every worker, then verify.

Our PHP2Shell explainer covers the flaws, the leak and the affected versions. This post answers a narrower question: if your provider has pushed a rule, how much time does it buy?

What the providers said

Several providers published statements on 21 October. The table records what each said about its own rules. It is not a comparison, and we have not tested any of these products.

ProviderWhat it says it shippedOn by default?What it says about patchingSource
CloudflareSeven "Generic Rules, Anomalous Execution" rules in the Free ruleset, seven in the Managed Ruleset, plus a response-side ruleYes on Free; paid plans asked to confirm block modePatch even with the rules onCloudflare blog and changelog
AkamaiAdaptive Security Engine rule 3000991 v1 for PHP RCE attacks on both CVEs; existing command injection rules also catching weaponised payloadsThe post says App and API Protector customers are protectedPrompt patching is "the most effective defense"Akamai
FastlyA CVE-specific templated virtual patch in its Next-Gen WAF, for Edge and on-prem deploymentsCustomers are told to apply it; opted-in customers get updates automaticallyCalls active mitigation partial; patching is the fixFastly
ImpervaA virtual patch; Cloud WAF covered out of the box, Elastic WAF and WAF Gateway via software and content updatesCloud WAF yes; others need updatesMitigations "do not replace patching"Imperva

We did not find a managed rule published by AWS. Classmethod engineer Suzuki Ryo published a test of custom AWS WAF rules that block multipart POSTs on a load balancer; its results, covered below, apply well beyond AWS.

Cloudflare's rules in detail

  • Timing. Cloudflare says it switched the rules on at 03:02 UTC on 21 October, about nine hours before public disclosure. PHP's fixes went live around 13:45 GMT, per Reuters via KSL.
  • Scope. Rules apply automatically on every plan with WAF, including Free. Paid customers on the Managed Ruleset are told to confirm the deployment is in block mode.
  • Naming. Neither changelog entry names PHP or either CVE, so searching for the CVE finds nothing. Use the rule IDs in Cloudflare's blog post. The changelog text also says four rules (Managed) and two (Free), while both tables list seven. Go by the IDs.
  • Response side. A response rule in the Cloudflare Managed Response Firewall Default ruleset uses what Cloudflare calls full transaction detection, matching a request with the origin's response to spot exploits that worked.
  • Attack traffic. Cloudflare says it observed attack traffic aimed at its customers after the leak, and that attackers mutated payloads to try to get past the rules. It gave no volumes.

Cloudflare's CTO Dane Knecht put it plainly on X, as quoted by Reuters via KSL: "This is a STOPGAP only." The blog post says to patch even with the rules on, and to keep them on while you do.

What the others added

Fastly's post, by Chief Product Officer Kelly Shortridge, says active mitigation is partial and may block a lot of legitimate traffic. It reports evidence of attacks before disclosure and expects new variants through at least next week.

Imperva says it saw no requests matching the patterns it tracked before disclosure. That differs from Cloudflare and Fastly, but the networks and patterns differ, so both can be true. Assume attempts are under way.

Why a WAF is a stopgap for this bug

This is not a knock on WAFs. The issue is where PHP2Shell lives.

  1. The flaw is in the runtime, not a URL. Many web bugs sit behind one endpoint a rule can block. PHP2Shell fires while PHP sets up and tears down a request; on mod_php, PHP says, before any application code runs. Any URL that reaches PHP is a candidate.

  2. Rules match shapes, and shapes change. Fastly says its detections look for bodies and parameters "shaped in ways that differ from legitimate traffic". Cloudflare says attackers were already mutating payloads. Today's rule may miss tomorrow's variant.

  3. Strict rules break real traffic. Multipart bodies and large form posts are also how real applications work. Every exception you add to keep uploads or checkout working is a gap.

  4. A response rule cannot undo code that already ran. Cloudflare's full transaction post says blocking a response cannot undo actions the origin already performed. It may hide the output from the attacker, but a written file or a spawned process stays.

  5. The WAF only sees what passes through it. Tenable notes Cloudflare's rules cover only traffic routed through Cloudflare. That holds for every edge product, and the next section is about it.

  6. Only the patch changes PHP. Per PHP's GitHub advisory, the fix removes the abrupt jump out of a request on out-of-memory and adds a soft limit and a higher hard exit. No edge rule can do that. Fastly adds that the underlying design issue remains after patching, a reason to keep layers on after you upgrade.

Origin exposure: the traffic your CDN never sees

A CDN protects a hostname. An attacker who finds the server behind it can skip the edge and talk to PHP directly. Common ways an origin stays reachable:

  • Direct IP access. The server answers on 80 or 443 to anyone. Cloudflare warns that historical DNS records may still expose pre-CDN addresses and suggests rotating origin IPs.
  • DNS-only records. A staging, admin or api hostname points straight at the same server. Cloudflare also says to audit SPF and TXT records, and notes that bounced email can reveal a server's IP.
  • A second path. A load balancer's own public endpoint, a cloud default hostname, a second CDN, or an internal network with broad reach.
  • Monitoring vhosts. An extra virtual host left open for an uptime checker often serves the same PHP pool.

Check your own origin

Run these from outside your network, against your own hostname and origin IP.

# What the public hostname resolves to (should be CDN addresses)
dig +short www.example.com

# Does the origin answer directly if you skip the CDN?
curl -sk -o /dev/null -w '%{http_code}\n' \
  --resolve www.example.com:443:203.0.113.10 https://www.example.com/

# Same check over plain HTTP
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: www.example.com' http://203.0.113.10/

A 200, 301 or 302 means the origin serves anyone who asks. You want a timeout or refused connection. On the server, check listeners and the host firewall:

sudo ss -ltnp | grep -E ':(80|443|8080|8443)\b'
sudo nft list ruleset 2>/dev/null || sudo iptables -S

Close the gap

Cloudflare's origin protection docs list several layers; other providers offer similar ideas under their own names.

ControlWhat it provesCaveat from Cloudflare's docs
Allowlist the provider's IP ranges at the firewallTraffic came from the provider's networkRated moderately secure; vulnerable to IP spoofing; you maintain the list
Authenticated Origin Pulls (mutual TLS)The request came through CloudflareNeeds Full or Full (strict) SSL; the default certificate only proves it came from Cloudflare, so upload your own for stricter checks
Cloudflare TunnelThe origin has no public listener at allNeeds the cloudflared daemon
Secret header validationThe request passed through your edge configModerately secure; header handling needs care
JWT validationThe request carries a valid tokenVery secure, but needs code changes or extra software

On a patch day, the fastest win is the host firewall: allow 80 and 443 only from your provider's published ranges and your management network, then rerun the curl test. Schedule mutual TLS or a tunnel afterwards.

The interim PHP settings, and why they are partial

PHP's advisory lists settings for teams that cannot upgrade at once, with a warning: "There are no complete mitigations, updating is the only safe choice." Here is what each closes and leaves, per php.net and the two GitHub advisories.

SettingTargetsWhat it closesWhat stays open
memory_limit = -1, set with php_admin_valueCVE-2026-109618The predictable per-request memory limit failureAn underlying allocation failure can still reach the vulnerable path, per php.net
post_max_size = 8M (32M with uploads), upload_max_filesize below itCVE-2026-109618Large bodies parsed by PHPGHSA notes a multipart request without a usable declared length can pass the first size check
Front-end limits on decoded size, field count, parts, headers, nestingCVE-2026-109618Oversized or deeply nested input before PHP sees itLowers OOM and DoS risk; does not replace updating
Block multipart at the front end if unusedCVE-2026-109618Multipart parsing entirelyBreaks uploads and many forms
session.upload_progress.enabled = 0CVE-2026-109619The demonstrated session pathOther lifecycle paths, per GHSA
fatal_error_backtraces = 0 (8.5 only)CVE-2026-109619The confirmed 8.5 backtrace pathOther request-owned global and extension state
Container or cgroup memory limitCVE-2026-109618An alternative to the PHP limitDoes not guarantee allocation failure; can kill the whole pool

Three warnings from the advisories are easy to miss:

  • Do not use RLIMIT_AS or RLIMIT_DATA. They make allocations fail at the limit, which is the exact condition CVE-2026-109618 needs.
  • Hiding errors does nothing. php.net says suppressing diagnostics with error_reporting, display_errors or the @ operator does not help.
  • Where you set it matters. Use php.ini, the FPM pool or server configuration. php.net says not to rely on ini_set() or FPM .user.ini, which apply too late.

For an FPM pool, that looks like this:

; /etc/php/8.3/fpm/pool.d/www.conf (path varies by distro)
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M
php_admin_value[upload_max_filesize] = 6M
php_admin_flag[session.upload_progress.enabled] = off

And for Apache with mod_php:

# In the vhost or a conf file loaded globally, not .htaccess
php_admin_value memory_limit -1
php_admin_value post_max_size 8M
php_admin_flag session.upload_progress.enabled off

Front-end limits go in the proxy; tune the numbers to your application:

client_max_body_size 8m;
large_client_header_buffers 4 8k;
LimitRequestBody 8388608
LimitRequestFields 50

Settings other vendors suggested

Fastly suggests max_input_nesting_level = 4 and, for PHP-FPM, pm.max_requests = 50. PHP's GitHub advisory mentions lowering nesting and considering pm.max_requests = 1, noting that recycling workers reduces success but does not remove the condition. Imperva suggests testing opcache.enable = 0, which php.net does not list. Test these under load and count none as a fix.

Also, memory_limit = -1 removes a guard rail: a leaking request can grow until the host steps in. Watch memory while it is set and restore a finite limit after patching.

Sequencing: stopgap, patch, restart, verify

Each step fails quietly if the one before was skipped. A patched package with old workers running is unpatched.

StageActionDone when
1. StopgapEdge rules on in block mode; origin locked to the provider; interim settings appliedDirect curl to origin fails; events show rule hits
2. PatchFixed PHP packages installed or images rebuiltChangelog or version shows the fix
3. RestartEvery PHP worker and server process replacedProcess start times are after the install
4. VerifyVersion, config, origin lock and logs checkedAll four checks pass on every host
5. UnwindTemporary settings reviewed; edge rules keptFinite memory limit restored; record updated

1. Stopgap, in parallel, within the hour

  1. Confirm your provider's rules are present and blocking. On Cloudflare, filter Security Analytics events by the rule IDs from its post. Paid plans should check the Managed Ruleset action.
  2. Run the origin checks above and close any direct path.
  3. Apply the interim PHP settings in pool or server config, then restart workers so they take effect.
  4. If a site cannot be patched today and the origin cannot be locked, follow the UK NCSC's advice as reported by Help Net Security: consider taking it offline. Our post on governments taking websites offline over PHP2Shell shows how public bodies made that call.

2. Patch

Install PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or your distro's backport. On distro packages, the version string may look old, so search the changelog for the CVE:

apt changelog php8.3-fpm | grep -m1 -E 'CVE-2026-10961[89]'
rpm -q --changelog php-fpm | grep -m1 -E 'CVE-2026-10961[89]'

For containers, pull the patched base image and rebuild. Our guide to finding and patching every PHP runtime covers fleets, images and hosting panels in depth.

3. Restart

A new binary on disk does nothing until the old processes exit.

sudo systemctl restart php8.3-fpm
sudo systemctl restart apache2   # or httpd
ps -o pid,lstart,cmd -C php-fpm8.3,apache2,httpd | head

Compare the start times with the time you installed the package. For containers, roll the deployment so every pod is replaced, not just new ones.

4. Verify

# Version of the serving binary, not just the CLI
php-fpm8.3 -v
docker run --rm --entrypoint php your-registry/app:prod -v

# Effective pool settings
grep -R -E 'php_(admin_)?(value|flag)' /etc/php/8.3/fpm/pool.d/

# Origin still locked after the restart
curl -sk -o /dev/null -w '%{http_code}\n' \
  --resolve www.example.com:443:203.0.113.10 https://www.example.com/

Then check logs for the signals Fastly described: out-of-memory errors that point at "Unknown on line 0", bursts of segfaults, and PHP workers spawning shell processes. Any of those before you patched means the host needs investigation, not just an upgrade.

5. Unwind carefully

Keep the edge rules on. Cloudflare recommends it, and they cost little. Put a finite memory_limit back once you are patched. PHP's advisory suggests keeping session upload progress off if you do not use it. Record which sites had open origins and fix them properly with mutual TLS or a tunnel.

What would have helped

The teams with the easiest 21 October already knew where PHP ran, whether each origin was reachable directly, and how to rebuild and restart in an hour.

Practices any team can use:

  • An origin map. For every public hostname, record the origin, whether it accepts direct traffic, and how the edge authenticates to it. Test it quarterly with the curl check above.
  • Origin authentication by default. Mutual TLS or a tunnel on every new site, so the WAF is the only way in.
  • A runtime inventory with SAPI and version. mod_php sites go first.
  • Interim settings prepared, not invented. A reviewed snippet per server type in the runbook.
  • A tested rebuild and roll path, with "process start time is after the patch" in the definition of done.
  • Edge rule hygiene. Know which managed rules are in log mode and who can switch them to block.
  • A pre-agreed offline decision. Who decides, and what users see.

Where Safeguard fits

Safeguard is not a WAF, does not see your traffic, and would not have blocked a PHP2Shell request. It helps with the part of the sequence that is about knowing what to patch and making the change.

  • Finding PHP in what you build. safeguard scan --image <name> builds an SBOM of a container image, including OS packages such as PHP, and matches components against vulnerability databases. safeguard scan --sbom <file> does the same for SBOMs from vendors, and safeguard scan --dir . covers repositories. Results are written as JSON under ./output.
  • Which projects ship this version. The platform shows which projects contain a given component and version, so "which images still carry old PHP" becomes a list.
  • Stopping regressions. Policies and gates evaluated in the platform can block a build that still carries a vulnerable PHP package, and safeguard exec runs checks in CI.
  • Making the change with approval. The Safeguard CLI's assistant can edit a Dockerfile to a patched base tag and run your build and tests with your approval. /permissions sets how much it may do alone: Ask, Accept edits, Plan, Auto (the default) or Strict. The desktop and phone apps can send that work to a computer running the CLI and approve each step.

Where it does not help: edge rules, origin firewalls, mutual TLS, php.ini settings on hosts, restarting workers, or spotting a compromise. Reachability analysis does not lower priority here, because the bug fires in the runtime before application code. Matching can also lag a same-day advisory, and distro backports keep old version strings, so confirm against your vendor's changelog.

Zero-Day Discovery and Gold

  • Zero-Day Discovery looks for previously unknown flaws before a CVE exists, in an agentic mode and a deterministic scanner mode, and issues findings as SGZ advisories before they reach public CVE databases. It covers seven package ecosystems. PHP core is a runtime, not a package in those ecosystems, so it falls outside that scope.
  • Gold Open Source (gold.safeguard.sh, free, no login) offers searchable CVE intelligence with CVSS, EPSS, KEV status, affected and fixed versions and a recommended safe version, plus a free read API and a CI gate GitHub Action.
  • Gold Registry (registry.safeguard.sh, paid) provides hardened drop-in components and container images held to zero exploitable critical or high CVEs, SLSA L3+ and Sigstore signed, with evidence pages. An artifact loses its badge on regression.

The habit that carries over: pull patched, signed components from a curated source and be able to rebuild and redeploy fast, so the WAF only has to hold for hours.

To try Safeguard, download it at https://safeguard.sh/download and read the CLI docs at https://docs.safeguard.sh/docs/cli .

Common mistakes and troubleshooting

  • "The rule is on, so we can patch next week." Every provider quoted here says to patch. The rule is for the gap between the leak and your restart.
  • Leaving paid rulesets in log mode. Log mode records the attack and lets it through.
  • Forgetting the staging hostname. A DNS-only record pointing to the same server is a full bypass.
  • Setting workarounds in .htaccess or .user.ini. Use server or pool config. On mod_php, the GitHub advisory says per-directory settings apply before request startup and can act as an early trigger.
  • Blocking all multipart POSTs on an app that takes uploads. Classmethod's test found a multipart block rule caught every variant from the first request, and also blocked forms with no file. Scope it to paths that never take uploads.
  • Relying on rate limits. In the same single-run test, rate-based rules let roughly 20 to 30 requests through before the first block. One request can be enough here.
  • Patching without restarting. The old code stays in memory.

FAQ

Do Cloudflare's PHP2Shell rules protect free plans?

Yes, according to Cloudflare. The rules deploy automatically on every plan with WAF, including Free, and the response-side rule applies to all plans. Cloudflare still says to patch.

Is a WAF virtual patch enough if I am on PHP 7.4 or older?

No. PHP published minimal source backports for 7.4, 8.0 and 8.1; older versions need a vendor backport or an upgrade. If neither is possible, lock the origin, apply the interim settings, and consider taking the site offline.

My site is behind a CDN. Why worry about the origin?

The CDN only inspects traffic sent to it. If the server answers on its own IP or a DNS-only hostname, an attacker can skip the edge. Run the direct curl test.

Will the response-side rules catch a successful exploit?

They may stop data from reaching the attacker. Cloudflare says blocking a response cannot undo actions the origin already performed, so code that ran still ran.

Should I disable OPcache?

Imperva suggests testing opcache.enable = 0, but php.net does not list it. Measure the performance cost first and do not count it as a mitigation.

Which interim setting helps most?

None is complete. Apply memory_limit = -1, request size limits and disabled session upload progress together, in server or pool config, then restart.

Fix it without waiting for upstream

In incidents like this one, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would have helped

These demos show real Safeguard sessions on other examples. None shows PHP2Shell or a WAF.

The CLI demo fixes a lodash finding: it reads the advisory, asks before each command, edits the version, runs install and tests with approval, and pushes nothing. For PHP2Shell, you would scan your PHP image, then ask the assistant to move the Dockerfile base tag to a fixed release and rebuild, approving each step. Edge rules and origin firewalls stay with your infrastructure team.

Click through it at your own pace, or open it full screen.

The local runner demo sets up the runner on your own machine, approves it with a device code, and scans from the terminal. Here, that fits step 4 of the sequence: after the rebuild, scan the new image locally and confirm the PHP package no longer matches either CVE before you roll it out.

Click through it at your own pace, or open it full screen.

The desktop app demo shows a CLI session in the conversation list, a request for work on your computer, choosing the folder, then Approve or Deny on each command and file edit, ending with an upgrade done and tests passing. On a patch day, the same flow suits a repo of Dockerfiles: you approve each base image change and build.

Click through it at your own pace, or open it full screen.

Quick check

Your site is behind a WAF with PHP2Shell rules, and a curl request sent straight to the origin IP returns 200. Are you protected?

No. The origin is serving PHP to anyone who skips the edge. Restrict the host firewall to your provider and management network, add origin authentication, and patch.

You set session.upload_progress.enabled = 0 in .user.ini. Does it count?

No. php.net says not to rely on ini_set() or FPM .user.ini. Set it in php.ini, the FPM pool or server config, restart every worker, and remember it only covers one CVE-2026-109619 path.

Sources

Last reviewed: 21 October 2026. This is a developing story; check the linked advisories for the latest status.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.