Skip to content

feat: PHP webshell detection with alert + IP ban - #11

Merged
vsilent merged 3 commits into
mainfrom
feature/php-shell-detection
Oct 31, 2026
Merged

vsilent merged 3 commits into
mainfrom
feature/php-shell-detection

Conversation

@vsilent

@vsilent vsilent commented Oct 31, 2026 •

Copy link
Copy Markdown

What

Adds PHP webshell detection to stackdog, for PHP apps on the host and in containers. The response is alert + IP ban only; files and containers are not quarantined.

  • File scan (src/detectors/php_shell/). Each sniff pass scores PHP files under host web roots (STACKDOG_PHPSHELL_PATHS) and under the docroots of PHP-looking containers (bind mounts plus overlay MergedDir, read through STACKDOG_HOST_ROOT). Results are cached by size/mtime in php_shell_scans. A file scoring ≥3 gives php.shell-file, which alerts and never bans.

  • Signatures: built-in weighted rules, plus extra rules loaded from JSON (STACKDOG_PHPSHELL_SIGNATURES) without a rebuild.

  • Access-log detector web.php-shell-access, which bans through the existing IpBanEngine gate (allowlist, protected local IPs, rate cap). It fires on:

    • a request to a flagged upload whose URL goes through uploads/;
    • a request with parameters to a flagged file that is not a common entry point;
    • a POST to PHP under an uploads/ URL.

    A plain visit to an infected page gives php.shell-request, which alerts and never bans.

  • Config: STACKDOG_PHPSHELL_ENABLED=false turns off both the scan and the access-log detector.

  • Docs: README section, CHANGELOG, .env.sample, and a commented /:/host:ro mount in docker-compose.yml.

  • Website: new /php-shell-detection page, a home feature card and FAQ entry, a footer link, the sitemap entry, and the new env vars on the docs page.

Commit c6caac4 fixes five findings from an earlier code review:

  • suffix matching banned site visitors;
  • a file named webshell.php could pass the AI-phrase ban check;
  • ENABLED=false left the access-log detector running;
  • /images/, /files/ and /tmp/ URLs banned on POST;
  • PATH_INFO and trailing-slash requests bypassed the detector.

Verification

  • cargo test: 444 + 8 + 108 passed, 0 failed, 1 ignored (the corpus test, run manually via STACKDOG_PHPSHELL_CORPUS; 0 findings on 501 real PHP files).
  • cargo clippy --all-targets -- -D warnings and cargo fmt --check: clean.
  • cd website && npm run build: OK. The page was checked at 390px with no horizontal overflow.

Not verified yet (needs a Linux test host)

  • Guess: the container MergedDir path is readable through STACKDOG_HOST_ROOT on a real overlay2 host. Only bind mounts are certain.
  • An end-to-end iptables/nftables ban: drop a test shell into uploads/, request it 5 times from a non-allowlisted IP, then check stackdog firewall list.
  • Only synthetic shell samples were tested, no real-world ones.

Out of scope

  • The existing web.webshell-probe false positive on stacker agent long-poll logs is not fixed here.
  • The eBPF "php-fpm spawns a shell" layer is planned, not implemented.

🤖 Generated with Claude Code

robotizeit and others added 3 commits October 31, 2026 11:48
Each sniff pass scores the PHP files in host web roots
(STACKDOG_PHPSHELL_PATHS) and in the docroots of PHP containers for
webshell code: request input reaching eval/system/assert, eval of
decoded data, request input called as a function, preg_replace /e,
and markers of known families. No single keyword flags a file; the
score does. New or changed files that score as a shell raise
php.shell-file, an alert: a file names no attacker.

Access logs supply the attacker. web.php-shell-access fires on any
request to a flagged file in an upload directory, a request with
parameters to a flagged file, or a POST to PHP in an upload directory,
and feeds the existing IP ban gate. A plain visit to an infected page
such as index.php is php.shell-request, alert only, so the site's
audience is not banned.

Containers are found by name/image or by a mount at a docroot; mounted
docroots are read at their host source and image docroots through the
overlay MergedDir, both under STACKDOG_HOST_ROOT when Stackdog runs in
Docker. Extra signatures load from JSON (STACKDOG_PHPSHELL_SIGNATURES).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New /php-shell-detection page: how detection works, the scored
signals, what alerts and what bans, host and Docker setup, settings,
an incident runbook and FAQ. Linked from a home-page feature card,
a home FAQ entry, the footer and the sitemap; the docs settings table
lists the new variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review findings on the PHP shell access detector:

- A flagged upload matched by URL suffix banned on any request, so a
  flagged uploads/index.php made the front page bannable. The ban now
  needs uploads/ in the URL, and a suffix that skips an uploads/ dir is
  not treated as the same file.
- php.shell-file and php.shell-request never ban. Before, they fell
  through to the AI phrase check, and a file named webshell.php passed it.
- STACKDOG_PHPSHELL_ENABLED=false also removes the access-log detector,
  not only the scan.
- On the URL side only uploads/ marks an upload dir. /images/, /files/
  and /tmp/ are ordinary URL trees.
- PATH_INFO, a trailing slash, and //, . and .. are resolved to the
  script before matching. PHP extensions match case-insensitively.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vsilent
vsilent merged commit 74ca8ec 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