编写高质量的 Nuclei 模板:从漏洞原理到自动化检测脚本
Nuclei 作为一个基于模板的、快速的漏洞扫描器,凭借其高度的灵活性和庞大的社区生态,已经成为安全工程师进行快速、大规模、定制化安全检测的。通过“白名单”机制,将用户的输入映射到一组已知的、安全的文件路径上,彻底杜绝目录遍历的可能性。的、快速、可定制的漏洞扫描工具。工作流可以将多个模板串联起来,实现“先 A 后 B”的检测逻辑,极大提升了检测的准确性。我们将针对刚刚搭建的本地文件包含(LFI)漏
前言
-
技术背景:在现代网络安全攻防体系中,资产发现与漏洞扫描是构建防御纵深和实施精准打击的基石。Nuclei 作为一个基于模板的、快速的漏洞扫描器,凭借其高度的灵活性和庞大的社区生态,已经成为安全工程师进行快速、大规模、定制化安全检测的核心基础设施之一。它填补了传统扫描器更新缓慢、无法快速响应 0-day/N-day 漏洞的空白。
-
学习价值:掌握 Nuclei 模板的编写,意味着你将拥有“将漏洞情报转化为自动化检测能力”的核心技能。你不再仅仅是工具的使用者,而是漏洞检测规则的创造者。学会后,你能够:
- 快速响应:为最新披露的漏洞(0-day/1-day)编写检测 PoC。
- 深度定制:为企业内部私有协议或特定应用场景创建专属的检测逻辑。
- 提升效率:将繁琐的手工验证流程自动化,实现大规模资产的快速筛查。
-
使用场景:Nuclei 模板的应用贯穿于网络安全的多个环节:
- 渗透测试:在授权测试中,对目标进行快速、全面的已知漏洞扫描。
- 安全运营(SecOps):集成到 CI/CD 流程或资产监控平台,实现对新增资产和线上服务的持续性漏洞监控。
- 红蓝对抗:作为攻击方快速建立滩头阵地的武器,或作为防守方验证自身防御体系有效性的工具。
- 漏洞赏金(Bug Bounty):自动化挖掘大规模目标中的常见漏洞,提高发现效率。
一、Nuclei 是什么
1. 精确定义
Nuclei 是一个由 ProjectDiscovery 团队开发的,基于 YAML 格式模板的、快速、可定制的漏洞扫描工具。它通过定义一系列请求和匹配规则,来验证目标主机是否存在特定的安全漏洞或配置缺陷。
2. 一个通俗类比
你可以把 Nuclei 想象成一个“智能体检机器人”,而 Nuclei 模板就是它的“体检项目清单”。
- 体检机器人 (Nuclei):它本身不知道该检查什么,但它非常高效,能严格按照清单指令去执行检查。
- 体检项目清单 (Nuclei 模板):这份清单由医生(安全研究员)编写,详细描述了如何检查某个具体的病症(漏洞)。例如,“让病人(目标服务器)做一个特定的动作(发送一个 HTTP 请求),然后观察他是否有特定的反应(响应包中包含‘SQL error’字样)”。
我们学习编写模板,就是学习如何编写这份精准、高效的“体检项目清单”。
3. 实际用途
- 快速验证 N-day 漏洞:当一个通用组件(如 Log4j, Fastjson)爆出漏洞后,可以迅速编写模板,对全网或企业内资产进行排查。
- 检测 Web 常见漏洞:如 SQL 注入、XSS、SSRF、文件上传等。
- 服务识别与指纹探测:识别特定服务、框架、中间件的版本信息。
- 配置安全检查:检查是否存在敏感文件泄露(如
.git目录)、未授权访问、错误的安全配置等。 - 超越 HTTP:Nuclei 支持 TCP、DNS、Websocket 等多种协议,可用于检测非 Web 服务的漏洞。
4. 技术本质说明
Nuclei 的技术本质是一个模式匹配引擎。它将漏洞验证过程抽象为两个核心部分:“如何发包(Requests)” 和 “如何匹配(Matchers)”。
- Requests:定义了如何与目标进行交互。对于 HTTP,这包括请求方法、路径、头部、请求体等。Nuclei 会按照模板中的定义,构造并发送一个或多个网络请求。
- Matchers:定义了如何判断交互结果是否符合漏洞特征。这可以是检查响应状态码、响应头、响应体内容(支持正则、关键词、二进制等),甚至是检查两者之间的逻辑关系(AND/OR)。
整个过程可以用下面的 Mermaid 流程图清晰地展示出来。
这张图清晰地展示了从用户运行命令到最终输出结果的完整 Nuclei 原理 工作流。
二、环境准备
1. 工具版本
为保证所有功能可用,建议使用最新的 Nuclei 版本。本文演示基于 Nuclei v3.2.x 或更高版本。
2. 下载方式
Nuclei 使用 Go 语言编写,提供了多种便捷的安装方式。
-
通过 Go (推荐):
# 确保你已安装 Go 环境 (version 1.21+) go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest -
通过 Homebrew (macOS):
brew install nuclei -
从 GitHub Releases 下载二进制文件:
访问https://github.com/projectdiscovery/nuclei/releases,根据你的操作系统下载对应的预编译二进制文件,解压后即可使用。
3. 核心配置命令
首次运行 Nuclei 时,它会自动下载官方的模板库到 ~/nuclei-templates 目录。你也可以手动更新。
-
更新模板库:
nuclei -update-templates -
验证安装:
nuclei -version如果能正确显示版本号,则表示安装成功。
4. 可运行环境 (Docker)
为了获得一个纯净、可复现的测试环境,强烈推荐使用 Docker。我们将搭建一个包含一个简单 Web 漏洞的靶场环境。
-
创建靶场文件:
新建一个目录nuclei-lab,并在其中创建index.php和Dockerfile。index.php:<?php // 一个简单的文件包含漏洞示例 // 仅限授权测试环境使用! $file = $_GET['file']; if (isset($file)) { // 在真实世界中,这里绝不能直接拼接用户输入! include($file); } else { echo "<h1>Welcome to Nuclei Lab</h1>"; echo "Try to include a file, e.g., ?file=index.php"; } ?>Dockerfile:# 使用官方 PHP + Apache 镜像 FROM php:8.0-apache # 将我们的漏洞代码复制到 web 根目录 COPY index.php /var/www/html/ # 暴露 80 端口 EXPOSE 80 -
构建并运行 Docker 容器:
在nuclei-lab目录下执行以下命令。# 构建镜像,命名为 nuclei-lab-app docker build -t nuclei-lab-app . # 以后台模式运行容器,并将容器的 80 端口映射到主机的 8080 端口 docker run -d -p 8080:80 --name nuclei-lab-instance nuclei-lab-app -
验证靶场:
访问http://localhost:8080,你应该能看到 “Welcome to Nuclei Lab”。
访问http://localhost:8080?file=index.php,页面会再次显示同样的内容,证明文件包含逻辑正在工作。我们的 Nuclei 实战 环境已准备就绪。
三、核心实战:编写第一个 LFI 漏洞检测模板
我们将针对刚刚搭建的本地文件包含(LFI)漏洞,编写一个完整的 Nuclei 模板。
1. 步骤一:创建模板文件
创建一个名为 my-first-lfi.yaml 的文件,并写入模板的基本信息。
# 模板唯一 ID,必须小写,不能有空格
id: my-first-lfi
# 模板元数据
info:
# 模板名称,会显示在结果中
name: My First LFI Test
# 作者
author: YourName
# 漏洞严重程度: info, low, medium, high, critical
severity: high
# 漏洞描述
description: A simple Local File Inclusion vulnerability test.
# 参考链接
reference:
- https://owasp.org/www-community/attacks/File_Inclusion
# 漏洞分类,便于管理
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
cvss-score: 7.5
cwe-id: CWE-22
# 标签,用于筛选模板
tags: lfi,php,lab,tutorial
目的:定义模板的“身份证”,包括它的名字、作者、严重性等,便于识别和管理。
2. 步骤二:定义 HTTP 请求
接下来,我们定义如何发送那个能触发漏洞的 HTTP 请求。
# http 是协议类型,此外还有 dns, tcp, file 等
http:
# 请求方法
- method: GET
# 请求路径,{{BaseURL}} 是 Nuclei 的内置变量,代表目标 URL
path:
- "{{BaseURL}}/?file=/etc/passwd"
# (可选) 定义多个请求,例如尝试不同的 payload
# - method: GET
# path:
# - "{{BaseURL}}/?file=C:\Windows\win.ini"
目的:指示 Nuclei 向目标的根路径发送一个 GET 请求,并在 file 参数中携带经典的 LFI payload /etc/passwd。
3. 步骤三:定义匹配规则
这是模板的核心,我们告诉 Nuclei 如何判断响应是否证明了漏洞的存在。
# 匹配器,定义如何判断漏洞是否存在
matchers-condition: and # 定义多个匹配器之间的逻辑关系,and 表示必须全部满足
matchers:
# 匹配器 1: 检查响应体
- type: regex # 匹配类型为正则表达式
part: body # 检查响应的 body 部分
# 正则表达式,匹配 /etc/passwd 文件的典型内容
regex:
- "root:x:0:0:"
# 匹配器 2: 检查状态码
- type: status # 匹配类型为状态码
part: status_code # 检查响应的状态码部分
status:
- 200
目的:设置两个必须同时满足的条件:
- 响应体中必须通过正则表达式匹配到
root:x:0:0:这样的内容,这是/etc/passwd文件的标志性特征。 - HTTP 响应状态码必须是
200。
4. 步骤四:运行与验证
现在,我们将完整的模板组合起来,并用它来扫描我们的靶场。
完整可运行示例 (my-first-lfi.yaml):
id: my-first-lfi
info:
name: My First LFI Test
author: YourName
severity: high
description: A simple Local File Inclusion vulnerability test.
reference:
- https://owasp.org/www-community/attacks/File_Inclusion
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
cvss-score: 7.5
cwe-id: CWE-22
tags: lfi,php,lab,tutorial
http:
- method: GET
path:
- "{{BaseURL}}/?file=/etc/passwd"
matchers-condition: and
matchers:
- type: regex
part: body
regex:
- "root:x:0:0:"
- type: status
part: status_code
status:
- 200
执行扫描命令:
# 警告:以下命令仅限在授权测试环境中使用!
# -t 指定我们编写的模板
# -u 指定目标 URL
nuclei -t my-first-lfi.yaml -u http://localhost:8080
预期输出结果:
[my-first-lfi] [http] [high] http://localhost:8080/?file=/etc/passwd
这个输出清晰地表明,my-first-lfi 模板在目标 http://localhost:8080 上发现了 high 级别的漏洞。
5. 自动化脚本
在真实场景中,我们通常需要扫描大量目标。下面是一个结合了 Nuclei 的简单自动化 Shell 脚本,演示了如何读取目标列表并进行扫描。
scan_targets.sh:
#!/bin/bash
# ==============================================================================
# Nuclei 批量扫描脚本
#
# 功能: 从一个文件中读取目标列表,并使用指定的 Nuclei 模板进行扫描。
#
# 警告: 本脚本仅限在已获得明确授权的渗透测试环境中使用。
# 未经授权的扫描是非法行为!
# ==============================================================================
# --- 参数定义 ---
# 模板文件路径,默认为 my-first-lfi.yaml
TEMPLATE_PATH="${1:-my-first-lfi.yaml}"
# 目标列表文件路径,默认为 targets.txt
TARGETS_FILE="${2:-targets.txt}"
# Nuclei 输出结果文件
OUTPUT_FILE="scan_results.txt"
# --- 错误处理与检查 ---
# 检查 Nuclei 是否已安装
if ! command -v nuclei &> /dev/null; then
echo "[ERROR] Nuclei 未安装或不在 PATH 环境变量中。请先安装 Nuclei。"
exit 1
fi
# 检查模板文件是否存在
if [ ! -f "$TEMPLATE_PATH" ]; then
echo "[ERROR] 模板文件 '$TEMPLATE_PATH' 不存在。"
exit 1
fi
# 检查目标文件是否存在
if [ ! -f "$TARGETS_FILE" ]; then
echo "[ERROR] 目标文件 '$TARGETS_FILE' 不存在。"
echo "请创建一个包含 URL 列表的 '$TARGETS_FILE' 文件,每行一个 URL。"
# 创建一个示例目标文件
echo "http://localhost:8080" > "$TARGETS_FILE"
echo "http://example.com" >> "$TARGETS_FILE"
echo "[INFO] 已为您创建一个示例目标文件 '$TARGETS_FILE'。"
fi
# --- 主逻辑 ---
echo "[INFO] 开始扫描..."
echo "[INFO] 使用模板: $TEMPLATE_PATH"
echo "[INFO] 扫描目标文件: $TARGETS_FILE"
echo "[INFO] 结果将保存到: $OUTPUT_FILE"
# 执行 Nuclei 命令
# -t: 指定模板
# -l: 指定目标列表文件
# -o: 指定输出文件
# -silent: 静默模式,只输出发现的漏洞
# -nc: 不显示颜色,便于文件输出
nuclei -t "$TEMPLATE_PATH" -l "$TARGETS_FILE" -o "$OUTPUT_FILE" -silent -nc
# --- 结果报告 ---
echo "[INFO] 扫描完成。"
if [ -s "$OUTPUT_FILE" ]; then
echo "[SUCCESS] 发现潜在漏洞,详情请查看文件: $OUTPUT_FILE"
cat "$OUTPUT_FILE"
else
echo "[INFO] 未发现任何漏洞。"
fi
exit 0
使用方法:
- 将以上脚本保存为
scan_targets.sh并给予执行权限chmod +x scan_targets.sh。 - 创建一个
targets.txt文件,每行放一个 URL,例如:http://localhost:8080 http://some-other-authorized-target.com - 运行脚本
./scan_targets.sh。它将使用my-first-lfi.yaml模板扫描targets.txt中的所有目标,并将结果保存在scan_results.txt中。
四、进阶技巧
1. 常见错误
- YAML 缩进错误:YAML 对缩进极其敏感。错误的缩进是新手最常遇到的问题,会导致模板解析失败。请务必使用空格(通常是 2 个)而不是 Tab,并保持层级结构清晰。
- Matcher 逻辑混乱:
matchers-condition默认为or。如果你希望所有条件都满足才算命中,必须显式设置为and。 - 正则表达式过于宽泛:一个糟糕的正则(如
.*)可能导致大量误报。例如,匹配<h1>可能会命中无数正常页面。正则应尽可能精确地锚定漏洞特征。
2. 性能 / 成功率优化
-
使用 Payloads 变量:当需要尝试多种 payload 时,不要为每个 payload 写一个 request 块。应使用
payloads变量和attack类型。# ... info ... http: - method: GET # 使用 {{payload}} 变量 path: - "{{BaseURL}}/?file={{payload}}" # 定义攻击类型为 clusterbomb,它会将所有 payload 应用到所有注入点 attack: clusterbomb # 定义 payload 变量 payloads: payload: # 从文件中加载 payload 列表 - "/etc/passwd" - "C:\\Windows\\win.ini" - "../../../../etc/passwd" # ... matchers ...这样做更简洁,也便于维护 payload 列表。
-
使用 Workflows 编排复杂逻辑:当一个漏洞的验证需要多个步骤时(例如,先登录,再访问后台地址),应使用
workflows。工作流可以将多个模板串联起来,实现“先 A 后 B”的检测逻辑,极大提升了检测的准确性。
3. 实战经验总结
- 先探测,后利用:一个高质量的模板应该首先发送一个无害的请求来探测应用指纹或特定路径是否存在,如果存在,再发送带有攻击 payload 的请求。这可以减少不必要的攻击流量,降低被 WAF 拦截的风险。
- 关注响应的独特性:最好的 Matcher 是匹配那些在正常响应中绝对不会出现,但在漏洞触发时必然会出现的特征。例如,一个特定的报错信息、一个版本号、一个 UUID 等。
- 利用动态变量提取:
extractors可以在一个请求的响应中提取信息(如 CSRF Token),并在后续的请求中使用。这是编写复杂漏洞(如需要认证的 RCE)模板的关键。
4. 对抗 / 绕过思路
-
绕过 WAF:
- 编码:使用 Nuclei 内置的编码函数(如
url_encode,base64_encode)对 payload 进行编码,可能绕过基于关键词的 WAF 规则。 - 分块传输 (Chunked Encoding):在
http请求块中添加unsafe: true和Host: {{Hostname}},然后手动构造包含Transfer-Encoding: chunked的请求头,可以尝试绕过一些不处理分块传输的 WAF。 - 请求参数污染 (HPP):发送多个同名参数,例如
?file=index.php&file=/etc/passwd,后端应用的处理方式可能与 WAF 不同,从而导致绕过。
- 编码:使用 Nuclei 内置的编码函数(如
-
隐藏扫描行为:
- 随机 User-Agent:在 Nuclei 配置文件或命令行中使用
-H "User-Agent: {{rand_ua()}}"来为每个请求使用随机的 User-Agent。 - 设置延迟和并发:通过
-c(并发数) 和-rl(速率限制) 参数降低扫描速度,模拟正常用户行为,避免因流量过大而被封禁 IP。
- 随机 User-Agent:在 Nuclei 配置文件或命令行中使用
五、注意事项与防御
1. 错误写法 vs 正确写法
| 场景 | ❌ 错误写法 (易误报、低效) | ✅ 正确写法 (精准、高效) |
|---|---|---|
| 匹配内容 | 匹配通用词,如 type: word, words: ["error"] |
匹配漏洞特有的、精确的报错信息,如 type: regex, regex: ["ODBC Driver.*Syntax error"] |
| 多 Payload | 为每个 payload 复制粘贴一个 http 请求块 |
使用 payloads 变量和 attack 类型来迭代 payload |
| 多步骤漏洞 | 试图在一个请求内完成所有事,或忽略前置条件 | 使用 workflows 编排多个模板,模拟真实攻击流程 |
| 路径探测 | 直接在 payload 中包含深层路径 ../../../../ |
使用 path 变量和 {{BaseURL}},让 Nuclei 自动处理路径拼接,并可结合路径模糊测试 |
2. 风险提示
- 法律风险:永远不要在未经授权的系统上运行 Nuclei! 这等同于黑客攻击,会带来严重的法律后果。
- 服务中断风险:某些模板(特别是 DoS 或未优化的模板)可能会对目标服务造成过大压力,导致其性能下降甚至宕机。务必在受控环境中测试模板。
- 数据污染:一些模板可能会向数据库写入测试数据(如 XSS 模板)。确保你有清理这些数据的方案。
3. 开发侧安全代码范式 (以 PHP LFI 为例)
防御我们刚刚利用的 LFI 漏洞,开发人员应遵循以下范式:
<?php
// 仅限授权测试环境使用!
// --- 安全代码范式 ---
// 1. 定义一个允许被包含文件的白名单
$allowed_files = [
'home' => 'pages/home.php',
'about' => 'pages/about.php',
'contact' => 'pages/contact.php'
];
// 2. 从用户输入中获取期望的页面标识,而不是文件名
$page = $_GET['page'] ?? 'home'; // 默认为 'home'
// 3. 检查用户输入是否在白名单中
if (array_key_exists($page, $allowed_files)) {
// 4. 如果在,则包含白名单中对应的、安全的、由服务器控制的文件路径
$file_to_include = $allowed_files[$page];
// 5. (可选但推荐) 再次确认文件存在,避免 include 报错
if (file_exists($file_to_include)) {
include($file_to_include);
} else {
// 记录错误日志
error_log("Attempted to include a non-existent file: " . $file_to_include);
http_response_code(404);
include('pages/404.php');
}
} else {
// 5. 如果用户输入不在白名单中,视为攻击或无效请求
// 记录安全事件日志
error_log("Security event: Invalid page parameter detected. Input: " . $page);
http_response_code(404);
include('pages/404.php');
}
?>
核心思想:永远不要相信用户的输入。通过“白名单”机制,将用户的输入映射到一组已知的、安全的文件路径上,彻底杜绝目录遍历的可能性。
4. 运维侧加固方案
- 部署 WAF/RASP:部署 Web 应用防火墙(WAF)或运行时应用自我保护(RASP),它们通常内置了对 LFI、SQLi 等常见攻击 payload 的检测规则。
- 最小权限原则:Web 服务器运行的用户(如
www-data)应被限制权限,使其无法读取 Web 根目录之外的敏感文件(如/etc/passwd,C:\Windows)。 - 禁用危险函数:在
php.ini中,通过disable_functions配置项禁用高风险的函数,如exec,shell_exec,passthru等,即使攻击者成功包含文件,也无法执行命令。 - 路径限制:在 PHP 中,使用
open_basedir配置可以限制 PHP 能够访问的文件系统路径,这是防御 LFI 的一道强力防线。
5. 日志检测线索
当发生 LFI 攻击时,Web 服务器的访问日志(access.log)和错误日志(error.log)会留下线索。
-
Access Log:
127.0.0.1 - - [21/Feb/2026:10:30:00 +0000] "GET /?file=../../../../etc/passwd HTTP/1.1" 200 3456 "-" "Nuclei - Open-source project"检测点:监控 GET/POST 请求参数中包含
../、..\\、/etc/passwd、win.ini等典型目录遍历和敏感文件名的请求。 -
Error Log (如果文件不存在或无权限):
[Sat Feb 21 10:31:00 2026] [php:warn] [pid 1234] [client 127.0.0.1:56789] PHP Warning: include(): Failed to open stream: No such file or directory in /var/www/html/index.php on line 6 [Sat Feb 21 10:31:00 2026] [php:warn] [pid 1234] [client 127.0.0.1:56789] PHP Warning: include(): Failed opening '../../../../etc/shadow' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/index.php on line 6检测点:监控 PHP
Warning日志,特别是Failed to open stream和Failed opening错误,其后跟着一个看起来像文件路径或遍历尝试的字符串。
总结
- 核心知识:Nuclei 模板的核心是 Requests (做什么) 和 Matchers (如何判断)。高质量的模板追求的是精准匹配,而非宽泛覆盖。
- 使用场景:从快速响应 0-day/N-day 漏洞,到企业内部的持续性安全监控和红蓝对抗,Nuclei 都是将漏洞情报转化为自动化检测能力的关键工具。
- 防御要点:防御 LFI 等漏洞的根本在于不信任用户输入。开发侧应采用“输入验证 + 白名单映射”的模式,运维侧应实施“最小权限 + 路径限制”的纵深防御。
- 知识体系连接:掌握 Nuclei 模板编写,是你从“工具使用者”迈向“规则创造者”的重要一步。它连接了漏洞原理分析、网络协议、代码审计和自动化运维等多个知识领域。
- 进阶方向:要成为 Nuclei 高手,下一步应深入学习
Workflows(用于复杂逻辑编排)、Extractors(用于动态数据提取)、以及Headless模板(用于检测需要浏览器渲染的漏洞),并尝试为 TCP、DNS 等非 HTTP 协议编写模板。
自检清单
- 是否说明技术价值?
- 是否给出学习目标?
- 是否有 Mermaid 核心机制图?
- 是否有可运行代码?
- 是否有防御示例?
- 是否连接知识体系?
- 是否避免模糊术语?
更多推荐
所有评论(0)