
Static config scanner that flags nginx configs vulnerable to the complex_value two-pass capture-clobbering bug (regex map + regex capture → heap overflow / info leak).
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/
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<name>...)) 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 runs before the
VALUE pass rewrites that capture, the two passes disagree:
mapBoth directions are remotely triggerable when the map input is
attacker-influenced ($request_body, $http_*, $arg_*, $uri,
$ssl_preread_server_name, and so on).
The scanner flags a location or server when all of these land in one two-pass buffer, in the exploitable order:
location ~, server_name ~, rewrite,
if (... ~ ...), or a regex map with a capture group.map (http or stream) with a ~ or ~* regex match key,
referenced by its output variable.return, set, add_header, proxy_method,
proxy_pass, rewrite, index, and others): both refs in the same value.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.# 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
| Option | Effect |
|---|---|
--json | Emit a JSON report instead of text. |
--no-includes | Do not follow include directives. |
| 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:
python3 nginx_capture_clobber_scan.py /etc/nginx/nginx.conf || echo "review needed"
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:
{
"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 <- regex-map on $http_user_agent [nginx.conf:30]"],
"reason": "..."
}
],
"cleared_safe_order": [],
"regex_maps": [],
"missing_includes": []
}
map variable in the same location or buffer as a
$1 to $9 or named-capture reference.(?P<name>...) group across multiple regex maps.proxy_set_header or
fastcgi_param family.set variables.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.Vulnerability write-up and exploit chain: https://cyberstan.co.uk/nginx-rce/