
ngxray — scanner de segurança de configuração do nginx
Scanner estático de vulnerabilidades para configurações do nginx. Analisa configurações com o tokenizador próprio do nginx e compara com regras JSON declarativas para detetar CVEs conhecidas em padrões de diretivas do motor de rewrite/script.
cd ngxray
git submodule update --init
make
python3 scan.py /etc/nginx/nginx.conf
As regras são definidas como ficheiros JSON em rules/. O scanner inclui:
| Regra | CVE | Padrão |
|---|---|---|
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 com $N sobrepostos |
is_argsAfetados: NGINX 0.6.27 – 1.30.0, NGINX Plus R32 – R36. Corrigido: 1.31.0 / 1.30.1.
Uma substituição rewrite que contém ? define e->is_args=1 no motor de script. Este flag persiste nas diretivas set ou if subsequentes, fazendo com que a passagem de cópia escape os dados de captura como URI (expansão de 3x) para um buffer dimensionado para bytes brutos.
location ~ ^/api/(.*)$ {
rewrite ^/api/(.*)$ /internal?migrated=true;
set $original_endpoint $1; # $1 evaluated with stale is_args=1
}
Afetados: até ao NGINX 1.31.0. Corrigido: 1.31.1 / 1.30.2.
Os grupos de captura aninhados fazem com que o orçamento de escape do fast-path conte a menos — os mesmos bytes de URI são escapados uma vez por referência $N sobreposta.
rewrite ^/((.*))$ http://backend/$1$2 redirect;
# Single file
python3 scan.py /etc/nginx/nginx.conf
# Directory (recursive, all *.conf files)
python3 scan.py /etc/nginx/
# Filenames from stdin
find /etc/nginx -name '*.conf' | python3 scan.py -
# Pre-parsed JSONL (faster for large batches)
find /etc/nginx -name '*.conf' | ./build/nginx_conf_parse - | python3 scan.py --jsonl -
# JSON output
python3 scan.py --json /etc/nginx/nginx.conf
# Custom rules directory
python3 scan.py --rules ./my-rules/ /etc/nginx/nginx.conf
O pacote corpus_tools/ pode recolher material público de configuração do nginx
a partir do GitHub Code Search e extrair excertos de nginx de formatos de wrapper.
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
Consulte corpus_tools/README.md e corpus_tools/docs/ para o fluxo de trabalho
completo e o método de recolha documentado focado em Rift.
Coloque um ficheiro JSON em rules/. O motor carrega todos os ficheiros *.json
do diretório de regras no arranque.
{
"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 regra pode incluir tests com excertos de configuração vulnerable (que devem acionar) e safe (que não devem acionar). Execute python3 scan.py --test para validar todas as regras.
match é uma lista de condições de diretiva. Se houver uma condição, o motor procura em toda a árvore de configuração por qualquer diretiva correspondente. Se houver várias, todas as condições têm de corresponder a diretivas irmãs no mesmo bloco.
Cada condição:
{
"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 posição (como string). Cada valor é um objeto de condição.max_args — rejeita se a diretiva tiver mais de N argumentos.overlapping_refs — verifica se as referências $N em args[1] correspondem a grupos de captura em args[0] que se contêm fisicamente uns aos outros.any_arg — a condição tem de corresponder a pelo menos um argumento em qualquer posição.or — pelo menos um ramo tem de corresponder. Os ramos podem verificar args ou any_arg_from + any_of.extract + min_unique é o mecanismo geral para contar padrões num argumento. Por exemplo, para exigir pelo menos 2 referências 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; } }"
]
}
}
O diretório parser/ contém um programa C autónomo que utiliza o tokenizador próprio do nginx (ngx_conf_read_token e ngx_conf_parse de src/core/ngx_conf_file.c) para analisar ficheiros de configuração e produzir uma AST JSON. Este é o mesmo lexer que o nginx utiliza no arranque — sem reimplementação.
A modificação principal: ngx_conf_handler() (que distribui diretivas para os módulos compilados) é substituída por conf_handler(), que regista todas as diretivas numa árvore independentemente do nome. Isto permite que o analisador processe qualquer configuração válida sem o sistema de módulos. A modificação é aplicada como um patch no momento da compilação — nenhum ficheiro de código-fonte do nginx é copiado para este repositório.
Os restantes ficheiros de código-fonte do nginx (alocador de pool, funções de string, registo de logs, etc.) são compilados diretamente a partir do submódulo.
scan.py carrega regras JSON de rules/, analisa configurações através de nginx_conf_parse e avalia cada regra contra a AST. Todos os detalhes de deteção estão nos ficheiros de regras JSON.
Requer um compilador C e Python 3. O código-fonte do nginx é incluído como um submódulo git.
git submodule update --init
make
make test
Este repositório é espelhado em modo só de leitura para github.com/califio/ngxray através do Copybara. Consulte COPYBARA.md.
| Chave | Tipo | Corresponde quando |
|---|
contains | string | O argumento contém a substring |
regex | string | O argumento corresponde à regex |
not_regex | string | O argumento NÃO corresponde à regex |
any_of | [string] | O argumento é igual a um dos valores |
extract | string | Regex com um grupo de captura — extrai todas as correspondências para contagem |
min_unique | int | (usado com extract) Pelo menos N correspondências distintas encontradas |