Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.2.9] - 2026-10-31

### Added

- `web.php2shell-attempt`: bans clients whose requests run PHP out of memory
before any script runs, the step every known PHP2Shell variant relies on.
Read from Apache error logs (`[client …] … Allowed memory size … in Unknown
on line 0`), nginx error logs in front of php-fpm (`upstream sent
unexpected FastCGI record … request: "POST …"`), and the public scanner's
`php2shell-…` User-Agent. Each line is one offense, so five in the ban
window ban the client in the same pass. Only the client and request are
kept from nginx lines: `server:` and `host:` can be this server's own
address.
- The `vuln.php2shell` alert now names the interim workaround,
`session.upload_progress.enabled = 0`.

## [0.2.8] - 2026-10-31

### Added
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "stackdog"
version = "0.2.8"
version = "0.2.9"
authors = ["Vasili Pascal <info@try.direct>"]
edition = "2021"
description = "Security platform for Docker containers and Linux servers"
Expand Down
2 changes: 1 addition & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Stackdog Security - Development Plan

**Last Updated:** 2026-04-07
**Current Version:** 0.2.8
**Current Version:** 0.2.9
**Status:** Phase 2 In Progress

## Project Vision
Expand Down
41 changes: 31 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Stackdog Security

![Version](https://img.shields.io/badge/version-0.2.8-blue.svg)
![Version](https://img.shields.io/badge/version-0.2.9-blue.svg)
![License](https://img.shields.io/badge/license-MIT-green.svg)
![Rust](https://img.shields.io/badge/rust-1.75+-orange.svg)
![Platform](https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-lightgrey.svg)
Expand Down Expand Up @@ -179,7 +179,7 @@ curl -fsSL https://raw.githubusercontent.com/trydirect/stackdog/main/install.sh

Pin a specific version:
```bash
curl -fsSL https://raw.githubusercontent.com/trydirect/stackdog/main/install.sh | sudo bash -s -- --version v0.2.8
curl -fsSL https://raw.githubusercontent.com/trydirect/stackdog/main/install.sh | sudo bash -s -- --version v0.2.9
```

If your repository has no published stable release yet, use `--version` explicitly.
Expand Down Expand Up @@ -623,18 +623,39 @@ fix is upgrading PHP; Stackdog tells you where you still have to.
|---|---|---|
| `vuln.php2shell` | a PHP build older than 8.2.35, 8.3.36, 8.4.27 or 8.5.12, or any 8.1 and older | Critical alert for upstream builds (official `php` images, read from `PHP_VERSION`); Medium "check your vendor" alert for distribution packages, which backport fixes without changing the version number |
| `php.worker-crash` | 3 or more php-fpm or Apache workers dying on SIGSEGV, SIGBUS, SIGABRT or SIGILL in one log source within one pass; 1–2 per pass never alert, so one flaky extension stays quiet | alert; repeated crashes are what attempts against PHP memory bugs leave behind |
| `web.php2shell-attempt` | the exploit's out-of-memory step, with the client address: Apache `[client …] PHP Fatal error: Allowed memory size … in Unknown on line 0`; nginx `upstream sent unexpected FastCGI record … client: …, request: "POST …"` (php-fpm); or the public scanner's `php2shell-…` User-Agent | alert + an offense per line, so a client causing 5 within the ban window is banned in the pass that reads them |

The version check reads `php8.x-common` (Debian, Ubuntu) and `php8x-common` or `php8x` (Alpine)
from the package database on the host and inside each running container, and
`PHP_VERSION` from container environments. RPM-based hosts are not covered.
Neither finding names a client, so neither bans. Turn the version check off
with `STACKDOG_PHP_VERSION_CHECK_ENABLED=false`.

Crash lines come from php-fpm and Apache error logs. Container logs are read
on their own (official images send php-fpm errors to stderr); add host logs
such as `/var/log/php8.3-fpm.log` to `STACKDOG_LOG_SOURCES`.

Stackdog does not detect the exploit request itself and cannot patch PHP.
The version and crash findings name no client, so they never ban. Turn the
version check off with `STACKDOG_PHP_VERSION_CHECK_ENABLED=false`.

`web.php2shell-attempt` bans. Every known PHP2Shell variant pushes PHP past
`memory_limit` with a multipart POST; when that happens while PHP is still
parsing the request, the error says `in Unknown on line 0`. An application
running out of memory names its script and line instead, and is never an
attempt. The two log formats were reproduced against `php:8.3-apache` and
`php:8.3-fpm` behind nginx with a benign oversized form POST, not taken from
exploit traffic. Limits:

- the exploit can succeed in a few requests, so a ban mostly stops repeats and
scanners, not a fast first attempt;
- the web server error log must be read: Docker container logs are, host logs
such as `/var/log/apache2/error.log` and `/var/log/nginx/error.log` go in
`STACKDOG_LOG_SOURCES`;
- behind a CDN or proxy, the error log must record the real client address;
- the same lines have rare innocent causes. A user posting a giant form to a
PHP with a very low `memory_limit` hits the Apache rule (at the default
128M that takes a body over 128 MB, which `client_max_body_size` usually
stops first). A buggy extension crashing php-fpm on an upload hits the
nginx rule. Either way it takes 5 in 5 minutes from one address;
- the scanner User-Agent comes from one report (Fastly, via Brandefense). If
the scanner changes it, that rule goes quiet; the two out-of-memory rules
do not depend on it.

Crash lines come from php-fpm and Apache error logs too; add host logs such as
`/var/log/php8.3-fpm.log`. Stackdog cannot patch PHP: upgrading is the fix.

---

Expand Down
2 changes: 1 addition & 1 deletion VERSION.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.8
0.2.9
2 changes: 1 addition & 1 deletion docs/INDEX.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Stackdog Security - Documentation Index

**Version:** 0.2.8
**Version:** 0.2.9
**Last Updated:** 2026-04-07

---
Expand Down
6 changes: 3 additions & 3 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#
# Usage:
# curl -fsSL https://raw.githubusercontent.com/trydirect/stackdog/main/install.sh | sudo bash
# curl -fsSL https://raw.githubusercontent.com/trydirect/stackdog/main/install.sh | sudo bash -s -- --version v0.2.8
# curl -fsSL https://raw.githubusercontent.com/trydirect/stackdog/main/install.sh | sudo bash -s -- --version v0.2.9
#
# Installs the stackdog binary to /usr/local/bin.
# Requires: curl, tar, sha256sum (or shasum), Linux x86_64 or aarch64.
Expand Down Expand Up @@ -73,7 +73,7 @@ resolve_version() {
fi

if [ -z "$TAG" ]; then
error "Could not determine latest release. Create a GitHub release, or specify one with --version (e.g. --version v0.2.8)."
error "Could not determine latest release. Create a GitHub release, or specify one with --version (e.g. --version v0.2.9)."
fi

VERSION="$(echo "$TAG" | sed 's/^v//')"
Expand Down Expand Up @@ -136,7 +136,7 @@ main() {
echo "Install stackdog binary to ${INSTALL_DIR}."
echo ""
echo "Options:"
echo " --version VERSION Install a specific version (e.g. v0.2.8)"
echo " --version VERSION Install a specific version (e.g. v0.2.9)"
echo " --help Show this help"
exit 0
;;
Expand Down
3 changes: 2 additions & 1 deletion src/detectors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use self::audits::{ConfigAssessmentMonitor, DockerPostureMonitor, PackageInvento
use self::http_request::{extract_request, HttpRequest};
use self::integrity::FileIntegrityMonitor;
use self::ml::MlBehavioralDetector;
use self::php2shell::{PhpVersionMonitor, PhpWorkerCrashDetector};
use self::php2shell::{Php2ShellAttemptDetector, PhpVersionMonitor, PhpWorkerCrashDetector};
use self::php_shell::{
ContainerFilesystem, PhpShellAccessDetector, PhpShellOptions, PhpShellScanner, ScanRoot,
};
Expand Down Expand Up @@ -146,6 +146,7 @@ impl DetectorRegistry {
self.php_shell_scanner.flagged_files(),
));
self.register(PhpWorkerCrashDetector);
self.register(Php2ShellAttemptDetector);
}

pub fn detect_log_anomalies(&self, entries: &[LogEntry]) -> Vec<LogAnomaly> {
Expand Down
Loading
Loading