Repository navigation
feat: PHP webshell detection with alert + IP ban - #11
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 overlayMergedDir, read throughSTACKDOG_HOST_ROOT). Results are cached by size/mtime inphp_shell_scans. A file scoring ≥3 givesphp.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 existingIpBanEnginegate (allowlist, protected local IPs, rate cap). It fires on:uploads/;uploads/URL.A plain visit to an infected page gives
php.shell-request, which alerts and never bans.Config:
STACKDOG_PHPSHELL_ENABLED=falseturns off both the scan and the access-log detector.Docs: README section, CHANGELOG,
.env.sample, and a commented/:/host:romount indocker-compose.yml.Website: new
/php-shell-detectionpage, a home feature card and FAQ entry, a footer link, the sitemap entry, and the new env vars on the docs page.Commit
c6caac4fixes five findings from an earlier code review:webshell.phpcould pass the AI-phrase ban check;ENABLED=falseleft the access-log detector running;/images/,/files/and/tmp/URLs banned on POST;Verification
cargo test: 444 + 8 + 108 passed, 0 failed, 1 ignored (the corpus test, run manually viaSTACKDOG_PHPSHELL_CORPUS; 0 findings on 501 real PHP files).cargo clippy --all-targets -- -D warningsandcargo 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)
MergedDirpath is readable throughSTACKDOG_HOST_ROOTon a real overlay2 host. Only bind mounts are certain.uploads/, request it 5 times from a non-allowlisted IP, then checkstackdog firewall list.Out of scope
web.webshell-probefalse positive on stacker agent long-poll logs is not fixed here.🤖 Generated with Claude Code