elastic / elastic/detection-rules
[New hunt] Web Root File Write Burst by a Web Server Process
- 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
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