One idea for quick-fix. Not very tested. Use at your own risk. Patch up to latest PHP versions ASAP.
…provided Apache has mod_security2 loaded and blocking is enabled.
Use:
SecRuleEngine On
SecRequestBodyAccess On
SecRule ARGS_NAMES "@rx (?i)^PHP_SESSION_UPLOAD_PROGRESS$" \
"id:10961801,phase:2,deny,status:403,log,msg:'PHP2Shell upload-progress blocked'"
phase:2 lets ModSecurity parse the multipart body, then rejects it before Apache forwards the request to PHP-FPM.
Verify:
- mod_security2 is installed/loaded.
- SecRuleEngine On is not DetectionOnly.
- The rule is in the active Apache vhost/include.
- ModSecurity audit logs show the block.
This blocks the known exploit shape, not every possible future variant.
The safest way to make it more general is to filter the request class, not endlessly expand the exploit string.
If neither site needs uploads
Block all multipart requests before PHP:
SecRule REQUEST_HEADERS:Content-Type \
"@rx (?i)^multipart/form-data(?:;|$)" \
"id:10961802,phase:1,deny,status:403,log,msg:'Multipart request blocked before PHP'"
This is broader and stronger, but breaks all uploads.
If uploads are needed
Allow multipart only on known upload URLs, and reject it everywhere else:
# Conceptually: allow /known/upload/path only
multipart request + URI not on allowlist = 403
This is the best balance.
Keep the specific PHP_SESSION_UPLOAD_PROGRESS rule as a second defense, and disable upload progress. Do not rely on a broader regex alone—the CVE is in PHP’s request handling, so only patching PHP fully fixes it.
verified this against the PHP advisory and ModSecurity documentation.
Correction: the broad multipart rule is valid Apache/ModSecurity syntax, but it is not a
general PHP2Shell defense. It only blocks attacks that use multipart/form-data.
The PHP advisory says:
- session.upload_progress=0 blocks the confirmed session-upload paths only.
- Request-size/structure limits reduce exposure.
- No complete mitigation exists; updating PHP is the only safe fix.
So the defensible Apache strategy is:
- Keep the exact parameter rule:
SecRule ARGS_NAMES "@streq PHP_SESSION_UPLOAD_PROGRESS" \ "id:10961801,phase:2,deny,status:403,log" - Disable session.upload_progress.
- If uploads are unnecessary, additionally block all multipart requests at phase 1.
- If uploads are needed, allow multipart only on explicitly known upload routes.
The exact ARGS_NAMES rule requires:
SecRuleEngine On
SecRequestBodyAccess On
and must be in active ModSecurity/vhost configuration. It will run before PHP-FPM.
Sources verified:
- PHP advisory: https://www.php.net/archive/2026.php#2026-10-21-5
- ModSecurity reference:
https://github.com/owasp-modsecurity/ModSecurity/wiki/Reference-Manual-(v2.x)
AI Skill: PHP2Shell hardening for a Docker site stack
- Apache edge container
- Enable ModSecurity.
- Enable request-body inspection.
- Block PHP_SESSION_UPLOAD_PROGRESS before proxying to PHP-FPM.
- If uploads are unnecessary, reject all multipart/form-data.
- Otherwise allow multipart only on approved upload routes.
- PHP-FPM container
session.upload_progress.enabled = 0 - Mount this as a conf.d override and recreate/restart PHP-FPM.
- Stack limits
- Set conservative request/body/upload limits.
- Apply Docker memory/process limits.
- Run PHP-FPM unprivileged.
- Verify
- Apache has security2_module.
- FPM reports session.upload_progress=0.
- Apache returns 403 for a test request containing PHP_SESSION_UPLOAD_PROGRESS.
- Status
- These are partial mitigations.
- Replace/backport PHP 5.6 as the real fix.
Apache filtering belongs in the Apache service; PHP configuration belongs in the PHP-FPM service.