Skip to content

feat: ban clients attempting PHP2Shell (v0.2.9) - #14

Merged
vsilent merged 2 commits into
mainfrom
feature/php2shell-attempt-ban
Oct 31, 2026
Merged

vsilent merged 2 commits into
mainfrom
feature/php2shell-attempt-ban

Conversation

@vsilent

@vsilent vsilent commented Oct 31, 2026

Copy link
Copy Markdown

What

web.php2shell-attempt (src/detectors/php2shell.rs) bans clients whose requests run PHP out of memory before any script runs. Every known PHP2Shell variant (CVE-2026-109618, CVE-2026-109619) relies on that step: multipart POSTs past memory_limit (Sansec, Calif).

Rule Log line Source of the format
Apache mod_php [client …] PHP Fatal error: Allowed memory size … in Unknown on line 0 reproduced locally
nginx → php-fpm upstream sent unexpected FastCGI record … client: …, request: "POST …" (php-fpm's own log has no client address) reproduced locally
scanner php2shell-* User-Agent, combined-format access lines only one report (Fastly via Brandefense)

Lab: php:8.3-apache, and nginx:alpine in front of php:8.3-fpm, with memory_limit = 4M and a benign 24 MB form POST. No exploit code was used. An application OOM reads … in /var/www/html/oom.php on line 1 and is not matched.

Ban mechanics

  • Each matching line is one offense, so 5 in the 5-minute window ban the client in the pass that reads them. The existing gate (allowlist, local IPs, rate cap) still applies.
  • Identical lines in one pass are numbered ((repeat N)), so registry dedup does not collapse an nginx burst into one offense.
  • From nginx lines only the timestamp, client: and request: are kept. server: and host: can be this server's own address, and every address in a sample gets an offense.

The vuln.php2shell alert now also names the interim workaround, session.upload_progress.enabled = 0.

The version is bumped to 0.2.9.

Review fixes on the branch

  • The User-Agent rule read the last quoted text of any line, so a log line ending in a file name like "php2shell-report.pdf" could ban an innocent client. It is now restricted to the combined access-log shape.
  • Identical lines were collapsed by detect_log_anomalies into one offense. Repeats are now numbered, and the ban-path test runs through the registry.
  • The documented limits now include the rare innocent causes and the single-source User-Agent.

Verification

  • cargo test: 460 + 8 + 108 passed, 0 failed, 1 ignored. cargo clippy --all-targets -- -D warnings and cargo fmt --check are clean. cd website && npm run build: OK.
  • End to end, the built binary ran stackdog sniff --once against the lab:
    • both out-of-memory rules fired, from exported log files and from the lab containers' Docker logs;
    • a live php:8.1-cli container got the Critical vuln.php2shell alert from its PHP_VERSION;
    • patched 8.3.36 containers stayed silent.

Not verified

  • No real ban: the lab client was a private address, which the ban gate never blocks. The firewall step is unchanged and covered by existing tests.
  • Guess: real exploit traffic produces the same lines, because it relies on the same OOM step. No exploit traffic was observed.
  • The exploit can succeed in a few requests, so a ban mostly stops repeats and scanners. Upgrading PHP stays the fix.

🤖 Generated with Claude Code

robotizeit and others added 2 commits October 31, 2026 19:01
Every known PHP2Shell variant pushes PHP past memory_limit with a
multipart POST. web.php2shell-attempt reads the lines that step leaves
with a client address, and bans:

- Apache mod_php: "[client ...] PHP Fatal error: Allowed memory size ...
  in Unknown on line 0". "Unknown on line 0" means PHP ran out of memory
  while parsing the request, before any script ran. Application OOMs
  name a script and line, and are not attempts.
- nginx in front of php-fpm: the worker exits after that fatal error,
  and nginx logs "upstream sent unexpected FastCGI record ... client:
  ..., request: "POST ..."". php-fpm's own log has no client address.
- the public scanner's php2shell-* User-Agent, read only from a
  combined-format access line.

Both log formats were reproduced locally against php:8.3-apache and
php:8.3-fpm behind nginx with memory_limit = 4M and a benign 24 MB form
POST, not taken from exploit traffic.

Each line is one offense. Identical lines in a pass are numbered so the
registry does not collapse them, so five in the ban window ban in the
pass that reads them. From nginx lines only the timestamp, client and
request are kept: server: and host: can be this server's own address,
and every address in a sample gets an offense.

The vuln.php2shell alert also names the interim workaround,
session.upload_progress.enabled = 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vsilent
vsilent merged commit a5ab4cb into main Oct 31, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants