Compensating controls.
If patching is impossible, reduce exposure while you prepare the runtime upgrade. Each example below addresses a limited attack surface; none is a substitute for the patch.
Read the scope first
The two flaws have multiple paths. A multipart block or upload-progress toggle does not cover all of them. Go to the patch guide →
The frontend examples deliberately reject all multipart/form-data requests in scope, across HTTP methods. They can break legitimate forms and uploads. Use them only where that traffic can be stopped; route necessary uploads to a patched or isolated runtime.
These are configuration templates, not vendor-certified PHP2Shell signatures. Adapt them in staging, test syntax and application behavior, and verify enforcement. Keep direct origin access behind the same control. The snippets have not been deployed or exploit-tested here.
PHP configuration
CONTROL / 01Why it helps: disabling upload progress removes confirmed session-related consumers of stale state; disabling PHP 8.5 fatal backtraces removes one confirmed producer. These settings do not repair memory lifetime handling or cover all variants. They are partial controls for CVE-2026-109619, not a fix for CVE-2026-109618.
; In the web runtime's php.ini, before request parsing.
; Partial control only. Restart every PHP worker.
session.upload_progress.enabled = Off
; PHP 8.5 only: confirmed fatal-backtrace path.
fatal_error_backtraces = Off; In each relevant PHP-FPM pool configuration.
php_admin_flag[session.upload_progress.enabled] = off
; PHP 8.5 pools only.
php_admin_flag[fatal_error_backtraces] = off- Disabling upload progress blocks confirmed session consumers of CVE-2026-109619, not every stale-state variant.
- Use php.ini, an FPM pool or server-level settings. ini_set() and FPM .user.ini are too late for these startup paths.
- Restart every worker; merely editing files leaves existing workers and stale state in service.
- Remove invalid/deprecated per-request INI settings that cause initialization diagnostics and fix attacker-triggered fatal errors/timeouts.
Verify: Use a private diagnostic through the actual web runtime to confirm PHP_VERSION, PHP_SAPI and the effective INI values; remove it afterward. Do not expose phpinfo().
Sources: Configuration reference
PHP OOM control
CONTROL / 02Why it helps: an enforced unlimited PHP memory limit removes a controllable per-request failure trigger. Real allocation failures can still reach vulnerable cleanup. The underlying bug remains until PHP is patched.
Setting memory_limit = -1 removes PHP’s memory cap. Establish a container/cgroup boundary first. An underlying allocation failure can still reach the vulnerable path; this is not complete remediation.
; FPM pool example: only while an upgrade is impossible.
; Establish a container/cgroup memory boundary first.
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M
php_admin_value[upload_max_filesize] = 2M- The upstream advisory proposes an enforced unlimited PHP memory_limit to remove the attacker-controlled per-request OOM trigger. Underlying allocation failure can still reach the flaw.
- This removes PHP's memory guardrail. Use a container/cgroup boundary and an operational policy appropriate to worker termination; do not replace it with RLIMIT_AS or RLIMIT_DATA.
- The size values are a no-upload example; review application needs. Upstream suggests 32M post_max_size for uploads and upload_max_filesize below it.
- Bound decoded request bodies and parser work at the frontend, including requests without a usable Content-Length. Body size alone does not bound multipart metadata expansion.
- Restart workers and check no pool, directory or application restores a finite PHP memory limit.
Sources: Configuration reference
Apache HTTP Server
CONTROL / 03# Inside each affected VirtualHost; requires mod_rewrite.
# Stops multipart forms and uploads for this virtual host.
RewriteEngine On
RewriteCond %{HTTP:Content-Type} multipart/form-data [NC]
RewriteRule ^ - [F,L]
# Optional body-size ceiling; example: 8 MiB.
LimitRequestBody 8388608- Keep the rule ahead of proxy/front-controller rewrite rules. Use server/VirtualHost configuration rather than depending on application code.
- A body ceiling reduces risk but does not bound PHP's internal memory expansion. Header filtering leaves non-multipart paths unaddressed.
- For mod_php, restart all Apache workers after PHP upgrades or runtime INI changes. For proxy_fcgi, restart the FPM pool as well.
Verify: apachectl configtest; after a successful result, reload using your service manager. Service and binary names vary by distribution.
Sources: Configuration reference
nginx
CONTROL / 04# In the http {} context.
map $http_content_type $php2shell_multipart {
default 0;
~*multipart/form-data 1;
}# Add to each existing server {} serving unpatched PHP.
# This intentionally disables multipart forms and uploads.
if ($php2shell_multipart) {
return 403;
}
client_max_body_size 8m;- The map belongs in http {}; the active if/return and body limit belong in the existing server {} before PHP/proxy handling.
- Add both nginx blocks in their indicated contexts; a map alone does not block requests.
- Test upload and API paths, including larger bodies and requests without Content-Length. A request-body size cap is not a cap on PHP memory use.
- Updating nginx does not update PHP-FPM; replace PHP workers separately.
Verify: nginx -t; after a successful result, reload nginx using your service manager. Restart PHP workers when changing their binary or INI.
Sources: Configuration reference
AWS WAF
CONTROL / 05# Add this item to AWS::WAFv2::WebACL Properties.Rules.
# Choose a free priority before any terminating Allow rule.
- Name: BlockMultipartToUnpatchedPHP
Priority: 10
Action:
Block: {}
Statement:
ByteMatchStatement:
SearchString: multipart/form-data
PositionalConstraint: CONTAINS
FieldToMatch:
Headers:
MatchPattern:
IncludedHeaders:
- content-type
MatchScope: VALUE
OversizeHandling: MATCH
TextTransformations:
- Priority: 0
Type: LOWERCASE
VisibilityConfig:
CloudWatchMetricsEnabled: true
MetricName: BlockMultipartToUnpatchedPHP
SampledRequestsEnabled: true- This is a CloudFormation fragment, not a complete stack or raw WAF API payload. Associate the ACL with every relevant CloudFront/ALB/API entry point.
- Headers inspection is capped at 8 KiB or 200 headers. MATCH blocks requests when that inspection limit is exceeded, which can reject otherwise legitimate traffic.
- Review rule priority and any Allow/Skip behavior. A Count action can measure impact but does not block traffic.
- It blocks the media type, not a full exploit signature, and does not patch any runtime.
Verify: Validate the full CloudFormation template, review the change set, then confirm sampled requests and WAF logs show a benign multipart probe being blocked. Verify direct origin access is restricted.
Sources: Configuration reference
lighttpd
CONTROL / 06# Add at server scope or inside the affected host condition.
# Ensure mod_access is already loaded in server.modules.
$REQUEST_HEADER["Content-Type"] =~ "(?i)multipart/form-data" {
url.access-allow = ( )
url.access-deny = ( "" )
}- The empty deny suffix matches every URL when the header condition is true. This stops all multipart forms/uploads in scope.
- Clearing url.access-allow in the matching condition prevents an inherited allow-suffix list from taking precedence over access-deny; review later matching conditions for overrides.
- The condition and allow/deny precedence were checked against current lighttpd source; validate compatibility with the installed release.
- Patch/restart the PHP FastCGI backend separately; changing lighttpd does not change the PHP runtime.
Verify: lighttpd -tt -f /etc/lighttpd/lighttpd.conf; after a successful result, reload using your service manager. Check the real configuration path for your distribution.
Sources: Configuration reference
GCP Cloud Armor
CONTROL / 07has(request.headers['content-type']) &&
request.headers['content-type'].lower().contains('multipart/form-data')- Use this as an advanced match expression with action deny(403) on the backend security policy protecting the application.
- Choose an unused priority before broad allow rules, attach the policy to the actual backend service, and restrict access that bypasses the load balancer.
- Cloud Armor joins repeated header values with commas, so the substring check also sees that representation.
- Preview mode reports matches only; turn preview off to enforce. This header rule avoids request-body inspection limits but only reduces multipart exposure.
Verify: Preview against legitimate requests, inspect security-policy logs, then enforce and verify that a benign multipart request is denied. Preview alone is not protection.
Sources: Configuration reference
Cloudflare
CONTROL / 08http.request.headers.truncated or
any(lower(http.request.headers["content-type"][*])[*] contains "multipart/form-data")- Use a WAF custom rule with action Block. This intentionally rejects all multipart traffic and requests whose headers were truncated for inspection.
- Keep the DNS record proxied and restrict direct origin traffic. Review earlier Skip rules and rule ordering.
- Do not block the PHPSESSID cookie: that breaks normal sessions and is not a reliable exploit indicator.
- Managed protections and narrower form-field rules are additional controls; do not describe any as a complete fix for both CVEs.
Verify: Validate the expression in the dashboard and check Security Events with a harmless multipart request. Confirm legitimate business flows after enforcement.
Sources: Configuration reference
Check the control without an exploit
Send an ordinary multipart request to your local test application. For the blanket block examples above, expect the configured rejection (usually HTTP 403) and a matching edge or web-server log entry.
curl --include --max-time 10 \
--form 'probe=php2shell-rule-check' \
--form 'file=@/dev/null;filename=probe.txt' \
http://127.0.0.1:8080/upload.phpThis checks request filtering only. It does not exercise the vulnerabilities, verify a PHP patch, or validate a rule that only matches a particular progress field. For AWS WAF, Cloud Armor, or Cloudflare, replace the localhost URL with your owned HTTPS test hostname that routes through that edge; localhost does not test cloud enforcement. Also test regular forms, APIs, header variants, and upload paths used by your application.
Return to the complete fix