
ngxray — сканер безопасности конфигурации nginx
Статический сканер уязвимостей для конфигураций nginx. Разбирает конфигурации с помощью собственного токенизатора nginx и сопоставляет их с декларативными JSON-правилами для обнаружения известных CVE в шаблонах директив rewrite/script engine.
cd ngxray
git submodule update --init
make
python3 scan.py /etc/nginx/nginx.conf
Правила определены в виде JSON-файлов в каталоге rules/. Сканер поставляется со следующими правилами:
| Правило | CVE | Шаблон |
|---|---|---|
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 с перекрывающимися ссылками $N |
is_args вызывает переполнениеЗатронутые версии: NGINX 0.6.27 – 1.30.0, NGINX Plus R32 – R36. Исправлено: 1.31.0 / 1.30.1.
Замена в rewrite, содержащая ?, устанавливает e->is_args=1 в скриптовом движке. Этот флаг сохраняется в последующих директивах set или if, из-за чего этап копирования выполняет URI-экранирование захваченных данных (трёхкратное расширение) в буфер, размер которого рассчитан на необработанные байты.
location ~ ^/api/(.*)$ {
rewrite ^/api/(.*)$ /internal?migrated=true;
set $original_endpoint $1; # $1 evaluated with stale is_args=1
}
Затронутые версии: вплоть до NGINX 1.31.0. Исправлено: 1.31.1 / 1.30.2.
Вложенные группы захвата приводят к занижению бюджета экранирования на быстром пути — одни и те же байты URI экранируются по одному разу для каждой перекрывающейся ссылки $N.
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
Пакет corpus_tools/ позволяет собирать публичные конфигурации nginx из GitHub Code Search и извлекать фрагменты nginx из форматов-обёрток.
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
Полное описание рабочего процесса и документированного метода сбора, ориентированного на Rift, см. в corpus_tools/README.md и corpus_tools/docs/.
Поместите JSON-файл в каталог rules/. Движок при запуске загружает все файлы *.json из каталога правил.
{
"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 { ... }"]
}
}
Каждое правило может включать tests с фрагментами конфигурации vulnerable (должны срабатывать) и safe (не должны срабатывать). Запустите python3 scan.py --test, чтобы проверить все правила.
match — это список условий для директив. Если условие одно, движок ищет во всём дереве конфигурации любую подходящую директиву. Если условий несколько, все они должны соответствовать соседним директивам в одном блоке.
Каждое условие:
{
"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 — индексируется по позиции (позиция — строка). Каждое значение — объект условия.max_args — отклонять директиву, если у неё больше N аргументов.overlapping_refs — проверяет, что ссылки $N в args[1] соответствуют группам захвата в args[0], которые физически вложены друг в друга.any_arg — условие должно совпасть хотя бы с одним аргументом на любой позиции.or — должно совпасть хотя бы одно ответвление. Ответвления могут проверять args или any_arg_from + any_of.extract + min_unique — это общий механизм подсчёта вхождений шаблона в аргументе. Например, чтобы потребовать как минимум 2 различные ссылки на группы захвата ($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; } }"
]
}
}
Каталог parser/ содержит автономную программу на C, которая использует собственный токенизатор nginx (ngx_conf_read_token и ngx_conf_parse из src/core/ngx_conf_file.c) для разбора конфигурационных файлов в JSON AST. Это тот же самый лексер, который nginx использует при запуске, — никакой повторной реализации.
Ключевая модификация: ngx_conf_handler() (который направляет директивы в скомпилированные модули) заменён на conf_handler(), который записывает каждую директиву в дерево независимо от её имени. Это позволяет парсеру обрабатывать любую корректную конфигурацию без системы модулей. Модификация применяется в виде патча во время сборки — исходные файлы nginx не копируются в этот репозиторий.
Остальные исходные файлы nginx (аллокатор пулов, строковые функции, журналирование и т.д.) компилируются непосредственно из субмодуля.
scan.py загружает JSON-правила из rules/, разбирает конфигурации через nginx_conf_parse и проверяет каждое правило по AST. Вся специфика обнаружения заключена в JSON-файлах правил.
Требуются компилятор C и Python 3. Исходники nginx включены в виде git-субмодуля.
git submodule update --init
make
make test
Этот репозиторий зеркалируется в режиме только для чтения на github.com/califio/ngxray через Copybara. См. COPYBARA.md.
| Ключ | Тип | Совпадает, когда |
|---|
contains | string | Аргумент содержит подстроку |
regex | string | Аргумент соответствует регулярному выражению |
not_regex | string | Аргумент НЕ соответствует регулярному выражению |
any_of | [string] | Аргумент равен одному из значений |
extract | string | Регулярное выражение с группой захвата — извлекает все совпадения для подсчёта |
min_unique | int | (используется с extract) Найдено как минимум N различных совпадений |