elastic / elastic/detection-rules

[New hunt] Web Root File Write Burst by a Web Server Process

Open
#6,598 1 comment 0 reactions 0 assignees View on GitHub
community
Dominant language
Python
Stars
2.7k
Forks
696
Avg merge
4d 17h
Merged PRs (30d)
87

Description

## Description

Identifies a burst of file writes under a web root by a web server process within a short window: a signal of mass webshell deployment or site defacement following exploitation, as opposed to a single webshell drop. A legitimate web server process ordinarily performs low-volume, incremental file writes (logs, cache, uploads); a sudden spike from the same process on the same host is anomalous.

**Behavior-based**, anomaly/volume-driven.

**Real testing performed**: real `nginx` + `php-fpm` stack, a webshell endpoint using PHP's own `file_put_contents()` (executed directly by the php-fpm worker process itself, not a forked child, which matters since the hunt's condition requires `process.name` to literally be the web server/PHP handler binary). Performed a real burst of 55 file writes under the real web root in quick succession (confirmed via real files present on disk afterward, owned by the actual `http` user the php-fpm worker runs as) and, separately, a real 3-write low-volume baseline for comparison against the `writes > 50` threshold.

## Target Huntset
linux

## Target hunt Type
ES|QL

## Query

```esql
FROM logs-endpoint.events.file-*
| WHERE @timestamp > NOW() - 1 hour AND event.action IN ("creation", "change")
AND (process.name LIKE "nginx*" OR process.name LIKE "php-fpm*" OR process.name LIKE "apache2*"
OR process.name LIKE "httpd*" OR process.name LIKE "lsphp*") AND file.path LIKE "*/www/*"
| STATS writes = COUNT() BY agent.id, process.name
| WHERE writes > 50
| SORT writes DESC
```

## Related issues or PRs
None yet. Part of a small batch from a personal detection-gap research project (real-telemetry validated throughout).

## References
- https://attack.mitre.org/techniques/T1505/003/

## Redacted Example Data

Real burst (55 real file writes performed directly by the php-fpm worker process, reshaped into ECS file-event shape since Elastic Defend's own file-integrity telemetry isn't available on the test host's kernel, noted transparently):
```json
{
"event": { "category": ["file"], "type": ["creation"], "action": "creation" },
"process": { "name": "php-fpm" },
"file": { "path": "/var/www/html/burst_1.txt" }
}
```
(× 55 real, distinct file paths within the same short window, same `process.name`/`agent.id`. Aggregating these with the hunt's own `STATS ... BY agent.id, process.name` produces `writes = 55`, correctly exceeding the `> 50` threshold.)

Real low-volume baseline (3 real writes, same mechanism, correctly stays under the threshold):
```json
{ "event": { "type": ["creation"] }, "process": { "name": "php-fpm" }, "file": { "path": "/var/www/html/lowvol_1.txt" } }
```

Known false-positive class (documented in the hunt's own notes): a legitimate CMS bulk import, cache-warming job, or deploy writing many static assets can also produce a real burst. Verify against known maintenance/deploy windows, and tune the `50`-write threshold to the target environment's actual baseline before treating a match as alert-worthy.

Contributor guide

Open the contributing guide

Research direction

Start with the ES|QL query and its target hunt type and Linux huntset, then review the real burst and low-volume examples in the issue. Validate that 55 writes from one web server process exceed the threshold while 3 writes do not, and document the CMS, cache-warming, and deployment false-positive cases before considering the hunt complete.

Written by the indexing model from the issue text.

Assessment

Tech stack
apache, elasticsearch, linux, nginx, php
Domain
security
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.