
ngxray — escáner de seguridad de configuración de nginx
Escáner de vulnerabilidades estáticas para configuraciones de nginx. Analiza configuraciones con el tokenizador propio de nginx y las compara con reglas JSON declarativas para detectar CVEs conocidas en patrones de directivas del motor de reescritura/scripts.
cd ngxray
git submodule update --init
make
python3 scan.py /etc/nginx/nginx.conf
Las reglas se definen como archivos JSON en rules/. El escáner incluye:
| Regla | CVE | Patrón |
|---|---|---|
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 con $N superpuestas |
is_args obsoletoAfectado: NGINX 0.6.27 – 1.30.0, NGINX Plus R32 – R36. Corregido: 1.31.0 / 1.30.1.
Una sustitución rewrite que contiene ? establece e->is_args=1 en el motor de scripts. Este indicador persiste en directivas set o if posteriores, lo que hace que el paso de copia aplique escape URI a los datos capturados (expansión 3x) en un búfer dimensionado para bytes sin procesar.
location ~ ^/api/(.*)$ {
rewrite ^/api/(.*)$ /internal?migrated=true;
set $original_endpoint $1; # $1 evaluado con is_args=1 obsoleto
}
Afectado: hasta NGINX 1.31.0. Corregido: 1.31.1 / 1.30.2.
Los grupos de captura anidados hacen que el presupuesto de escape de la ruta rápida subestime: los mismos bytes de URI se escapan una vez por cada referencia $N superpuesta.
rewrite ^/((.*))$ http://backend/$1$2 redirect;
# Archivo único
python3 scan.py /etc/nginx/nginx.conf
# Directorio (recursivo, todos los archivos *.conf)
python3 scan.py /etc/nginx/
# Nombres de archivo desde stdin
find /etc/nginx -name '*.conf' | python3 scan.py -
# JSONL preanalizado (más rápido para lotes grandes)
find /etc/nginx -name '*.conf' | ./build/nginx_conf_parse - | python3 scan.py --jsonl -
# Salida JSON
python3 scan.py --json /etc/nginx/nginx.conf
# Directorio de reglas personalizado
python3 scan.py --rules ./my-rules/ /etc/nginx/nginx.conf
El paquete corpus_tools/ puede recopilar material público de configuración de nginx desde GitHub Code Search y extraer fragmentos de nginx de formatos contenedores.
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
Consulta corpus_tools/README.md y corpus_tools/docs/ para ver el flujo de trabajo completo y el método de recopilación documentado centrado en Rift.
Coloca un archivo JSON en rules/. El motor carga todos los archivos *.json del directorio de reglas al inicio.
{
"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 { ... }"]
}
}
Cada regla puede incluir tests con fragmentos de configuración vulnerable (deben disparar) y safe (no deben disparar). Ejecuta python3 scan.py --test para validar todas las reglas.
match es una lista de condiciones de directivas. Si hay una sola condición, el motor busca en todo el árbol de configuración cualquier directiva que coincida. Si hay varias, todas las condiciones deben coincidir con directivas hermanas en el mismo bloque.
Cada condición:
{
"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 — indexado por posición (como cadena). Cada valor es un objeto de condición.max_args — rechaza si la directiva tiene más de N argumentos.overlapping_refs — verifica que las referencias $N en args[1] correspondan a grupos de captura en args[0] que se contengan físicamente entre sí.any_arg — la condición debe coincidir con al menos un argumento en cualquier posición.or — al menos una rama debe coincidir. Las ramas pueden comprobar args o any_arg_from + any_of.extract + min_unique es el mecanismo general para contar patrones en un argumento. Por ejemplo, para exigir al menos 2 referencias de captura distintas ($1–$9):
{ "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; } }"
]
}
}
El directorio parser/ contiene un programa C independiente que utiliza el tokenizador propio de nginx (ngx_conf_read_token y ngx_conf_parse de src/core/ngx_conf_file.c) para analizar archivos de configuración en un AST JSON. Este es el mismo analizador léxico que nginx usa al inicio, sin reimplementación.
La modificación clave: ngx_conf_handler() (que distribuye directivas a los módulos compilados) se reemplaza por conf_handler(), que registra cada directiva en un árbol independientemente de su nombre. Esto permite que el analizador procese cualquier configuración válida sin el sistema de módulos. La modificación se aplica como un parche en tiempo de compilación; no se copian archivos fuente de nginx en este repositorio.
Los archivos fuente restantes de nginx (asignador de pool, funciones de cadena, registro, etc.) se compilan directamente desde el submódulo.
scan.py carga reglas JSON desde rules/, analiza configuraciones mediante nginx_conf_parse y evalúa cada regla contra el AST. Todos los detalles de detección residen en los archivos de reglas JSON.
Requiere un compilador de C y Python 3. El código fuente de nginx se incluye como un submódulo de git.
git submodule update --init
make
make test
Este repositorio se replica de solo lectura en github.com/califio/ngxray mediante Copybara. Consulta COPYBARA.md.
| Clave | Tipo | Coincide cuando |
|---|
contains | string | El argumento contiene la subcadena |
regex | string | El argumento coincide con la expresión regular |
not_regex | string | El argumento NO coincide con la expresión regular |
any_of | [string] | El argumento es igual a uno de los valores |
extract | string | Expresión regular con un grupo de captura: extrae todas las coincidencias para contarlas |
min_unique | int | (se usa con extract) Se encuentran al menos N coincidencias distintas |