用于 nginx 配置的静态漏洞扫描器。使用 nginx 自身的分词器解析配置,并根据声明式 JSON 规则进行匹配,以检测 rewrite/脚本引擎指令模式中的已知 CVE。
cd ngxray
git submodule update --init
make
python3 scan.py /etc/nginx/nginx.conf
规则以 rules/ 目录中的 JSON 文件形式定义。扫描器内置以下规则:
| 规则 | 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) |
受影响: 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 转义(3 倍扩展)到为原始字节大小分配的缓冲区中。
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/ 包可以从 GitHub 代码搜索中收集公开的 nginx 配置资料,并从包装格式中提取 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 — 验证 args[1] 中的 $N 引用是否与 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 自身的分词器(来自 src/core/ngx_conf_file.c 的 ngx_conf_read_token 和 ngx_conf_parse)将配置文件解析为 JSON AST。这与 nginx 在启动时使用的词法分析器完全相同——没有重新实现。
关键修改:ngx_conf_handler()(负责将指令分派给内置模块)被替换为 conf_handler(),后者将所有指令(无论名称如何)记录到树中。这样解析器无需模块系统即可处理任何有效配置。该修改在构建时以补丁形式应用——本仓库不复制任何 nginx 源文件。
其余 nginx 源文件(池分配器、字符串函数、日志记录等)直接从子模块编译。
scan.py 从 rules/ 加载 JSON 规则,通过 nginx_conf_parse 解析配置,并根据 AST 评估每条规则。所有检测细节都存在于 JSON 规则文件中。
需要 C 编译器和 Python 3。nginx 源码以 git 子模块形式包含在内。
git submodule update --init
make
make test
本仓库通过 Copybara 只读镜像到 github.com/califio/ngxray。参见 COPYBARA.md。
| 键 | 类型 | 匹配条件 |
|---|
contains | string | 参数包含该子字符串 |
regex | string | 参数匹配该正则表达式 |
not_regex | string | 参数不匹配该正则表达式 |
any_of | [string] | 参数等于其中一个值 |
extract | string | 带捕获组的正则表达式——提取所有匹配项以进行计数 |
min_unique | int | (与 extract 配合使用)至少找到 N 个不同的匹配项 |