## https://sploitus.com/exploit?id=02F17C73-9E14-5B00-90C8-EEA4B95E677C
# nginx capture-clobber scanner
A read-only static scanner that flags nginx configurations vulnerable to the
`complex_value` two-pass capture-clobbering bug class (F5/nginx SIRT advisory,
July 2026). It parses your config and reports the vulnerable pattern with the
reason and location. It does not exploit anything and does not touch the running
server.
Single file, standard library only, Python 3.6+.
Full write-up of the vulnerability:
https://cyberstan.co.uk/nginx-rce/
## The bug
nginx builds many directive values in two passes. A LEN pass measures the length
and allocates a buffer, then a VALUE pass writes into it. Regex captures (`$1`
to `$9`, or named `(?P...)`) are read from per-request shared state. A
`map` whose match key is a regular expression runs a fresh PCRE match the moment
its variable is evaluated, and that match overwrites the shared capture state.
If a capture is measured in the LEN pass and then a regex `map` runs before the
VALUE pass rewrites that capture, the two passes disagree:
- clobber makes the value longer than measured: heap buffer overflow
(attacker-controlled out-of-bounds write)
- clobber makes the value shorter than measured: uninitialised heap returned to
the client (info leak of libc/heap pointers)
Both directions are remotely triggerable when the map input is
attacker-influenced (`$request_body`, `$http_*`, `$arg_*`, `$uri`,
`$ssl_preread_server_name`, and so on).
## What counts as vulnerable
The scanner flags a location or server when all of these land in one two-pass
buffer, in the exploitable order:
1. Capture source: a regex `location ~`, `server_name ~`, `rewrite`,
`if (... ~ ...)`, or a regex `map` with a capture group.
2. Clobber trigger: a `map` (http or stream) with a `~` or `~*` regex match key,
referenced by its output variable.
3. Two-pass sink using both a capture ref and the regex-map variable:
- single-value directives (`return`, `set`, `add_header`, `proxy_method`,
`proxy_pass`, `rewrite`, `index`, and others): both refs in the same value.
- the param/header families (`proxy_set_header`, `proxy_set_body`,
`fastcgi_param`, `scgi_param`, `uwsgi_param`, `grpc_set_header`): these
build one buffer per request for the whole family, so the two refs can be
in separate directives in the same location.
4. Order: the capture must be evaluated before the regex map. A regex map runs
once and caches, so map-before-capture reads a consistent state and is not
vulnerable. The scanner reports those as cleared (safe but fragile, do not
reorder).
## Usage
```sh
# scan a single config (follows include directives by default)
python3 nginx_capture_clobber_scan.py /etc/nginx/nginx.conf
# scan a directory (uses nginx.conf if present, else all *.conf)
python3 nginx_capture_clobber_scan.py /etc/nginx/
# scan several roots
python3 nginx_capture_clobber_scan.py site-a.conf site-b.conf
# machine-readable output
python3 nginx_capture_clobber_scan.py /etc/nginx/nginx.conf --json
# do not expand include directives
python3 nginx_capture_clobber_scan.py /etc/nginx/nginx.conf --no-includes
```
### Options
| Option | Effect |
|-----------------|---------------------------------------|
| `--json` | Emit a JSON report instead of text. |
| `--no-includes` | Do not follow `include` directives. |
### Exit codes
| Code | Meaning |
|------|----------------------------------------------|
| `0` | No vulnerable configuration found. |
| `1` | At least one vulnerable configuration found. |
| `2` | Usage error (no path given, or none found). |
Usable in CI, since a vulnerable config exits non-zero:
```sh
python3 nginx_capture_clobber_scan.py /etc/nginx/nginx.conf || echo "review needed"
```
## Output
Text mode prints, for each finding: file and line, the enclosing scope, the sink
directive, the capture and map variables involved, the triggering map, and a
one-line reason. A cleared section lists safe-order coexistences. `--json`
produces:
```json
{
"vulnerable": true,
"findings": [
{
"file": "/etc/nginx/conf.d/api.conf",
"line": 12,
"scope": "location ~ ^/api/(.+)$",
"directive": "proxy (family buffer)",
"captures": ["$1"],
"map_vars": ["$is_bot"],
"triggers": ["$is_bot ...)` group across multiple regex maps.
- Keep capture refs and regex-map refs out of the same `proxy_set_header` or
`fastcgi_param` family.
## Limitations
- Static analysis: it reads config, not runtime behaviour. It cannot resolve
values produced by dynamically `set` variables.
- Unreadable `include` targets (for example absolute paths that do not exist
locally) are reported under `missing_includes`, not silently skipped. Run it
on the host where the config resolves for full coverage.
- A flagged capture-before-map hit can be defused at runtime if that map
variable was already evaluated earlier in the same request (it would already
be cached). The scanner still reports these, which is the conservative choice
for a security self-scan.
## Reference
Vulnerability write-up and exploit chain:
https://cyberstan.co.uk/nginx-rce/