
ngxray — nginx config security scanner
Statischer Schwachstellenscanner für nginx-Konfigurationen. Analysiert Konfigurationen mit nginx' eigenem Tokenizer und gleicht sie mit deklarativen JSON-Regeln ab, um bekannte CVEs in Rewrite-/Script-Engine-Direktiven-Mustern zu erkennen.
cd ngxray
git submodule update --init
make
python3 scan.py /etc/nginx/nginx.conf
Regeln sind als JSON-Dateien in rules/ definiert. Der Scanner wird mitgeliefert mit:
| Regel | CVE | Muster |
|---|---|---|
rewrite-is-args | CVE-2026-42945 | rewrite … /path?args + set $var $1 |
rewrite-is-args-if | CVE-2026-42945 | rewrite … /path?args + if (… $1 …) |
nested-capture-redirect | CVE-2026-9256 | rewrite ^/((…))$ … redirect mit überlappenden $N |
is_args Überlauf des Stale-FlagsBetroffen: NGINX 0.6.27 – 1.30.0, NGINX Plus R32 – R36. Behoben: 1.31.0 / 1.30.1.
Ein rewrite-Ersatz, der ? enthält, setzt e->is_args=1 in der Script-Engine. Dieses Flag bleibt in nachfolgenden set- oder if-Direktiven erhalten, was dazu führt, dass der Kopierdurchlauf die Capture-Daten URI-escaped (3-fache Expansion) in einen Puffer mit Größe für Rohbytes kopiert.
location ~ ^/api/(.*)$ {
rewrite ^/api/(.*)$ /internal?migrated=true;
set $original_endpoint $1; # $1 evaluated with stale is_args=1
}
Betroffen: bis NGINX 1.31.0. Behoben: 1.31.1 / 1.30.2.
Verschachtelte Capture-Gruppen führen dazu, dass das Fast-Path-Escape-Budget unterzählt wird – dieselben URI-Bytes werden pro überlappender $N-Referenz einmal escaped.
rewrite ^/((.*))$ http://backend/$1$2 redirect;
# Einzelne Datei
python3 scan.py /etc/nginx/nginx.conf
# Verzeichnis (rekursiv, alle *.conf-Dateien)
python3 scan.py /etc/nginx/
# Dateinamen von stdin
find /etc/nginx -name '*.conf' | python3 scan.py -
# Vorparsierte JSONL (schneller für große Batches)
find /etc/nginx -name '*.conf' | ./build/nginx_conf_parse - | python3 scan.py --jsonl -
# JSON-Ausgabe
python3 scan.py --json /etc/nginx/nginx.conf
# Benutzerdefiniertes Regelverzeichnis
python3 scan.py --rules ./my-rules/ /etc/nginx/nginx.conf
Das Paket corpus_tools/ kann öffentliche nginx-Konfigurationsmaterialien aus der GitHub-Code-Suche sammeln und nginx-Ausschnitte aus Wrapper-Formaten extrahieren.
python3 corpus_tools/collect_github_nginx_corpus.py \
--output-dir corpus_out/nginx-rift \
--query-file corpus_tools/queries/nginx-rift.txt
python3 corpus_tools/extract_nginx_configs.py \
--input-dir corpus_out/nginx-rift/raw \
--output-dir corpus_out/nginx-rift/extracted
python3 scan.py corpus_out/nginx-rift/extracted
Siehe corpus_tools/README.md und corpus_tools/docs/ für den vollständigen Workflow und die dokumentierte, auf Rift fokussierte Sammelmethode.
Legen Sie eine JSON-Datei in rules/ ab. Die Engine lädt beim Start alle *.json-Dateien aus dem Regelverzeichnis.
{
"id": "rule-name",
"cve": "CVE-YYYY-NNNNN",
"severity": "CRITICAL",
"message": "short description shown in output",
"affected": "nginx version range",
"fixed": "fixed versions",
"ref": "https://link-to-advisory",
"match": [ ... ],
"tests": {
"vulnerable": ["server { ... }"],
"safe": ["server { ... }"]
}
}
Jede Regel kann tests mit vulnerable (muss auslösen) und safe (darf nicht auslösen) Konfigurationsausschnitten enthalten. Führen Sie python3 scan.py --test aus, um alle Regeln zu validieren.
match ist eine Liste von Direktivenbedingungen. Wenn es eine Bedingung gibt, durchsucht die Engine den gesamten Konfigurationsbaum nach einer passenden Direktive. Bei mehreren Bedingungen müssen alle Bedingungen mit Geschwisterdirektiven im selben Block übereinstimmen.
Jede Bedingung:
{
"directive": "rewrite",
"args": {
"0": { "contains": "?" },
"1": { "regex": "\\$[1-9]" }
},
"any_arg": { "regex": "\\$[1-9]" },
"or": [
{ "args": { "1": { "contains": "?" } } },
{ "any_arg_from": 2, "any_of": ["redirect", "permanent"] }
]
}
args — nach Position (als String) indiziert. Jeder Wert ist ein Bedingungsobjekt.max_args — ablehnen, wenn die Direktive mehr als N Argumente hat.overlapping_refs — überprüfen, ob $N-Referenzen in args[1] Capture-Gruppen in args[0] entsprechen, die sich physisch gegenseitig enthalten.any_arg — die Bedingung muss mindestens ein Argument an einer beliebigen Position treffen.or — mindestens ein Zweig muss zutreffen. Zweige können args oder any_arg_from + any_of prüfen.extract + min_unique ist der allgemeine Mechanismus zum Zählen von Mustern in einem Argument. Um beispielsweise mindestens 2 unterschiedliche Capture-Referenzen ($1–$9) zu erfordern:
{ "extract": "\\$([1-9])", "min_unique": 2 }
{
"id": "proxy-buffer-overflow",
"cve": "CVE-2099-99999",
"severity": "CRITICAL",
"message": "proxy_pass with oversized buffer — heap overflow",
"ref": "https://example.com/advisory",
"match": [
{
"directive": "proxy_pass",
"args": { "0": { "regex": "https?://" } }
},
{
"directive": "proxy_buffer_size",
"args": { "0": { "regex": "^(6[5-9]|[7-9]\\d|\\d{3,})k$" } }
}
],
"tests": {
"vulnerable": [
"server { location / { proxy_pass http://backend; proxy_buffer_size 128k; } }"
],
"safe": [
"server { location / { proxy_pass http://backend; proxy_buffer_size 8k; } }"
]
}
}
Das Verzeichnis parser/ enthält ein eigenständiges C-Programm, das nginx' eigenen Tokenizer (ngx_conf_read_token und ngx_conf_parse aus src/core/ngx_conf_file.c) verwendet, um Konfigurationsdateien in einen JSON-AST zu parsen. Dies ist derselbe Lexer, den nginx beim Start verwendet – keine Neuimplementierung.
Die wichtigste Änderung: ngx_conf_handler() (das Direktiven an eingebaute Module verteilt) wird durch conf_handler() ersetzt, das jede Direktive unabhängig vom Namen in einem Baum aufzeichnet. Dadurch kann der Parser jede gültige Konfiguration ohne das Modulsystem verarbeiten. Die Änderung wird zur Build-Zeit als Patch angewendet – keine nginx-Quelldateien werden in dieses Repository kopiert.
Die übrigen nginx-Quelldateien (Pool-Allokator, String-Funktionen, Logging usw.) werden direkt aus dem Submodul kompiliert.
scan.py lädt JSON-Regeln aus rules/, parst Konfigurationen über nginx_conf_parse und wertet jede Regel gegen den AST aus. Alle Erkennungsdetails befinden sich in den JSON-Regeldateien.
Erfordert einen C-Compiler und Python 3. Der nginx-Quellcode ist als Git-Submodul enthalten.
git submodule update --init
make
make test
Dieses Repository wird über Copybara schreibgeschützt auf github.com/califio/ngxray gespiegelt. Siehe COPYBARA.md.
| Schlüssel | Typ | Trifft zu wenn |
|---|
contains | string | Arg enthält die Teilzeichenfolge |
regex | string | Arg entspricht dem Regex |
not_regex | string | Arg entspricht NICHT dem Regex |
any_of | [string] | Arg entspricht einem der Werte |
extract | string | Regex mit einer Capture-Gruppe – extrahiert alle Übereinstimmungen zum Zählen |
min_unique | int | (verwendet mit extract) Mindestens N unterschiedliche Übereinstimmungen gefunden |