Nuclei Templates实战指南:从漏洞扫描到自动化安全验证平台
1. 项目概述:从“扫描器”到“自动化安全验证平台”的认知升级
提到Nuclei,很多刚接触安全测试的朋友第一反应是:“哦,那个很火的漏洞扫描器。” 这个认知对,但也不全对。在我过去几年的红队和渗透测试项目中,Nuclei早已从一个单纯的PoC(概念验证)扫描工具,演变成了我们团队日常工作中不可或缺的自动化安全验证与资产梳理平台。而这一切能力的核心,都源于它的“灵魂”——Templates(模板)。
简单来说,Nuclei本身是一个强大的引擎,但它“能做什么”、“做得多好”,完全取决于你给它喂什么样的Templates。你可以把它想象成一把多功能军刀,刀柄(引擎)是固定的,但上面能安装什么工具头(模板),决定了你是用它来开罐头、拧螺丝还是剪电线。网络上流传的成千上万个模板,就是这些功能各异的工具头。
对于安全工程师、运维人员甚至开发自测来说,掌握Nuclei Templates的使用,意味着你拥有了一种将已知漏洞、配置错误、信息泄露等安全问题的检测能力,快速转化为自动化脚本的能力。无论是应急响应时快速排查全网是否存在某个0day漏洞,还是日常巡检中批量检查子域名、中间件版本,或是验证WAF规则是否生效,Templates都是最高效的武器。
然而,我发现很多新手会卡在几个地方:模板从哪来?怎么写?怎么调试?为什么别人的模板跑得出结果,我的就不行?这篇内容,我就结合自己踩过的坑和实战经验,带你系统性地掌握Nuclei Templates,让你不仅能“用”,更能“改”和“创”,真正把自动化安全检测能力握在自己手里。
2. Nuclei Templates 核心机制深度解析
要玩转模板,必须先理解引擎的工作原理。这能帮你从根本上避开大多数坑。
2.1 模板的结构:YAML 里的“攻击逻辑”
一个Nuclei模板就是一个YAML文件,它严格遵循一套预定义的语法结构。别被YAML吓到,它的核心模块非常清晰。我们以一个最简单的检测网站标题的模板为例,拆开看:
id: http-title-detection info: name: HTTP Title Detection author: yourname severity: info description: Extracts the title from HTTP responses. http: - method: GET path: - "{{BaseURL}}" matchers: - type: word part: body words: - "<title>"id: 模板的唯一标识,全局不能重复。建议用组织名-漏洞类型-组件名的格式,如myteam-cve-2023-1234-apache。info: 元信息区。这里最重要的是severity(严重等级),它直接影响扫描报告的优先级。等级分为:info(信息)、low(低)、medium(中)、high(高)、critical(严重)。实操心得:不要滥用high和critical,只有真正能导致RCE(远程代码执行)、严重数据泄露的漏洞才用。否则在大型扫描中会制造大量“噪音”,让真正的高危告警被淹没。http: 这是模板的“心脏”,定义了HTTP请求的所有细节。method、path、headers、body都在这里配置。{{BaseURL}}是一个变量,运行时会被替换成目标URL。matchers: 匹配器,决定如何判断请求是否成功(即是否存在漏洞)。上例中,我们匹配响应体(part: body)中是否包含“<title>”这个词(word)。匹配器类型还有regex(正则)、status(状态码)、size(响应大小)等。
为什么这么设计?这种将攻击向量(http块)和结果判断(matchers块)分离的设计非常巧妙。它使得编写一个检测逻辑变得像填空一样简单:我发起一个什么样的请求(可能是一个特定的API路径,或一个带有恶意参数的POST请求),然后我期望在响应中看到什么(特定的字符串、状态码或正则匹配模式)。这种声明式的语法,大大降低了安全自动化脚本的编写门槛。
2.2 匹配器(Matchers)与提取器(Extractors):不仅仅是“有没有”,更是“是什么”
matchers告诉你目标“是否存在”某个特征,而extractors则能帮你从响应中“提取出”具体的信息,这是模板进阶使用的关键。
假设我们要检测一个暴露的phpinfo页面,并顺便提取出PHP版本和系统路径:
http: - method: GET path: - "{{BaseURL}}/phpinfo.php" matchers: - type: word words: - "PHP Version" - "System" condition: and # 必须同时满足两个关键词 extractors: - type: regex name: php_version part: body regex: - 'PHP Version (\d+\.\d+\.\d+)' group: 1 - type: regex name: system_path part: body regex: - '<tr><td class="e">System </td><td class="v">([^<]+)</td></tr>' group: 1在这个模板中:
- 匹配器:检查响应体中是否同时包含“PHP Version”和“System”这两个词,以此高置信度地判断这是一个
phpinfo页面。 - 提取器:使用两个正则表达式,分别从页面HTML中提取出PHP版本号和系统路径,并将结果命名为
php_version和system_path。这些提取到的信息会清晰地展示在扫描结果中。
注意事项:正则表达式虽然强大,但编写和维护成本高,且容易因页面微小的HTML结构变化而失效。优先使用word或dsl匹配器。dsl(领域特定语言)是Nuclei提供的一个更强大的匹配方式,允许你进行复杂的逻辑判断,比如检查响应时间、JSON字段值等,功能性强且不易出错。
2.3 动态负载(Payloads)与模糊测试(Fuzzing)
这是Nuclei模板真正发挥威力的地方。静态的检测是有限的,很多漏洞需要尝试不同的参数或载荷才能触发。
http: - method: POST path: - "{{BaseURL}}/api/user/login" body: 'username={{username}}&password={{password}}' payloads: username: - admin - administrator - test password: - admin123 - password - 123456 - "{{username}}123" # 支持使用另一个payload变量 matchers: - type: status status: - 200 - type: word words: - "\"success\":true" - "登录成功" condition: or这个模板模拟了一个简单的登录接口暴力破解。payloads块定义了两个字典:username和password。Nuclei会自动进行笛卡尔积式的组合爆破(3x4=12次请求)。在body中,我们使用{{username}}和{{password}}来引用这些动态载荷。
更高级的用法是配合raw请求和fuzzing。对于复杂的HTTP请求(如多层JSON、SOAP),你可以将整个请求包保存为一个文件,在模板中引用,并在其中标记需要模糊测试的位置:
http: - raw: - | POST /graphql HTTP/1.1 Host: {{Host}} Content-Type: application/json {"query": "query { user(id: \"§id§\") { name } }"} fuzzing: - part: body type: replace mode: single fuzz: - "1" - "' or '1'='1" - "\" or 1=1--"这里,§id§就是一个模糊测试点,Nuclei会用fuzz列表中的值依次替换它进行测试。这种方式非常适合测试SQL注入、NoSQL注入、GraphQL注入等需要精确控制请求格式的场景。
3. 模板的获取、管理与自定义开发实战
3.1 官方与社区模板库:站在巨人的肩膀上
最权威的模板来源是Nuclei官方项目: projectdiscovery/nuclei-templates 。这个仓库由ProjectDiscovery团队和维护者社区共同更新,质量较高,覆盖了CVE漏洞、错误配置、默认凭据、敏感信息泄露等几乎所有常见场景。
使用方法:
# 初始化,会克隆官方模板库到 ~/.local/nuclei-templates nuclei -ut # 更新模板 nuclei -update-templates但是,直接使用公共模板库存在风险:
- 误报与漏报:社区模板质量参差不齐,有些匹配条件过于宽松或严格,可能产生大量误报或漏掉真实漏洞。
- 法律与授权风险:一些模板具有攻击性(如登录爆破、数据遍历)。在未获得明确授权的情况下,对第三方资产使用这类模板是违法的。
- 不适合内部环境:公共模板主要针对互联网通用组件,对于公司内部自研的、特有的系统、API或中间件,几乎没有现成的检测能力。
因此,建立自己的模板仓库是必经之路。我建议的路径是:以官方库为学习和参考基础,逐步筛选、优化、积累属于自己团队或业务的专用模板库。
3.2 搭建私有模板工作流
我的团队使用Git来管理私有模板库,结构如下:
my-nuclei-templates/ ├── workflows/ # 工作流文件,用于组合多个模板 ├── cves/ # CVE漏洞检测模板 ├── exposures/ # 信息泄露、配置错误模板 ├── misconfig/ # 错误配置模板 ├── fuzzing/ # 模糊测试模板 ├── internal/ # 内部业务系统专用模板 │ ├── api-xxx/ │ └── app-yyy/ └── scripts/ # 辅助脚本,如模板校验、批量测试关键工具:nuclei -validate在将模板加入仓库前,必须使用验证命令检查语法:
nuclei -validate -t path/to/your-template.yaml它能帮你发现YAML格式错误、字段拼写错误等低级问题,避免模板失效。
3.3 手把手编写一个高质量模板:以检测Spring Boot Actuator未授权访问为例
让我们从零开始编写一个实用模板。Spring Boot Actuator端点未授权访问是一个经典的安全问题,可能导致敏感信息泄露。
第一步:明确检测逻辑我们需要检测几个常见的Actuator端点(如/actuator,/actuator/env等)是否在未认证的情况下可访问,并且响应内容符合Actuator的特征。
第二步:编写模板
id: springboot-actuator-unauth info: name: Spring Boot Actuator Unauthorized Access author: yourname severity: medium description: Detects unauthorized access to Spring Boot Actuator endpoints. reference: https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html tags: springboot,actuator,exposure http: - method: GET path: - "{{BaseURL}}/actuator" - "{{BaseURL}}/actuator/env" - "{{BaseURL}}/actuator/health" - "{{BaseURL}}/actuator/metrics" - "{{BaseURL}}/actuator/beans" matchers-condition: or # 只要任意一个路径匹配成功,就报告 matchers: # 匹配器1:通过状态码和响应体关键词判断 - type: word part: body words: - "_links" - "beans" - "status" - "UP" condition: or status: - 200 # 匹配器2:针对 /actuator 根路径,可能返回链接列表 - type: word part: body words: - "actuator" status: - 200第三步:优化与增强上面的模板有误报风险,比如一个普通的JSON API也返回{"status": "UP"}。我们需要增加更精确的匹配条件,利用Actuator响应的独特性。
matchers: # 更精确的匹配:结合路径特征和响应体特征 - type: dsl dsl: - 'contains(body, "_links") && contains(body, "actuator")' # /actuator 根目录 - 'contains(body, "propertySources") && contains(body, "systemProperties")' # /actuator/env - 'contains(body, "\"status\":\"UP\"") && status_code == 200' # /actuator/health 的典型JSON响应 - 'contains(body, "names") && contains(body, "mem")' # /actuator/metrics condition: or使用dsl匹配器,我们可以编写更灵活、更精确的逻辑组合,大幅降低误报率。
第四步:添加信息提取如果检测到/actuator/env,我们可以尝试提取一些环境变量,这些信息对后续渗透可能很有帮助。
extractors: - type: regex part: body name: spring_profiles regex: - '"profiles":\["([^"]+)"\]' group: 1 - type: regex part: body name: java_version regex: - '"java\.version":"([^"]+)"' group: 1编写心得:
- 从简单开始:先用宽泛的匹配确保能发现目标,再逐步增加精确条件减少误报。
- 善用
dsl:dsl匹配器功能强大,是编写高质量模板的利器,多查阅官方文档熟悉其函数。 - 利用
tags:给模板打上合适的标签(如springboot,exposure),便于后期通过-tags参数进行筛选扫描。 - 写好
description和reference:这不仅是为了别人能看懂,几个月后你自己回头看时,也能快速回忆起这个模板的用途和背景。
4. 高级技巧与实战场景应用
4.1 工作流(Workflows):串联检测逻辑
单个模板是“点”的检测,工作流则是“线”甚至“面”的自动化。工作流允许你定义模板的执行顺序和依赖关系。
场景:你想先检测目标是否是WordPress,如果是,再执行一系列WordPress相关的漏洞检测。
id: wordpress-audit-workflow info: name: WordPress Audit Workflow author: yourname # 逻辑:串行执行 workflows: - template: technologies/wordpress-detect.yaml # 步骤1:识别 matchers: - name: wordpress-detected # 只有当这个模板的匹配器名为‘wordpress-detected’的结果为真时,才继续 - template: vulnerabilities/wordpress/plugin-xss.yaml # 步骤2:插件XSS检测 - template: vulnerabilities/wordpress/core-rce.yaml # 步骤3:核心RCE检测 - template: exposures/wp-config-disclosure.yaml # 步骤4:配置文件泄露检测工作流的价值:
- 提升效率:避免对非WordPress站点运行无用的WordPress检测模板。
- 逻辑编排:可以实现“信息收集->漏洞检测->利用验证”的链条。
- 条件执行:基于上一步的结果(通过
matchers名称引用)决定是否执行下一步。
4.2 针对复杂认证场景的处理
很多内部系统需要登录后才能访问。Nuclei支持多种认证方式。
1. 使用-H头注入Cookie或Token(最简单):
nuclei -u https://target.com -t my-auth-test.yaml -H "Authorization: Bearer eyJhbGciOiJ..."在模板中,这个头部会自动添加到每个请求。
2. 在模板中定义静态头部:
http: - method: GET path: ["{{BaseURL}}/api/admin"] headers: Authorization: "Bearer {{token}}"然后在命令行传入-var token=eyJhbGciOiJ...。
3. 使用http模块的attack类型进行认证爆破(危险!需授权): 这通常用于测试登录接口的弱口令。你需要精心设计payloads和matchers来区分登录成功和失败。
重要警告:涉及认证和爆破的模板,必须在获得明确书面授权的前提下,在测试环境中使用。滥用可能导致账号锁定、法律风险。
4.3 与现有工具链集成
Nuclei不是孤岛,它可以完美融入现有的DevSecOps或安全运营流程。
输入集成:Nuclei可以从多种来源读取目标。
# 从文件读取URL nuclei -l urls.txt -t exposures/ # 从Subfinder、Assetfinder等子域名枚举工具接收结果 subfinder -d example.com -silent | nuclei -t cves/ -o results.txt # 从其他扫描器(如Naabu)的端口结果构造URL naabu -host example.com -silent | httpx -silent | nuclei -t exposures/输出集成:Nuclei支持JSON、Markdown、CSV等多种格式,方便导入到JIRA、DefectDojo、Elastic Stack等平台进行告警管理和数据分析。
nuclei -u example.com -t cves/ -o results.json -json
我团队的典型工作流:
- 使用
subfinder+httpx获取活跃的域名和URL列表。 - 使用一套基础的
exposures/和misconfig/模板进行第一轮快速筛查,发现明显弱点。 - 对重要的资产,使用完整的模板库(CVE、fuzzing等)进行深度扫描。
- 将JSON格式的扫描结果通过脚本自动导入到内部的安全管理平台,生成工单并指派。
5. 常见问题、调试技巧与性能优化实录
即使理解了所有原理,在实际编写和运行模板时,你依然会遇到各种问题。下面是我总结的“排坑指南”。
5.1 模板不报结果?一步步调试
这是最常见的问题。请按以下顺序排查:
- 语法验证:首先运行
nuclei -validate -t template.yaml,确保没有语法错误。 - 单个目标测试:使用
-u对一个确定存在问题的目标进行测试,排除目标不对的问题。 - 启用详细调试:使用
-debug或-debug-req、-debug-resp标志。
这会打印出Nuclei发送的每一个请求和接收到的原始响应。这是最强大的调试手段。你可以清晰地看到:nuclei -u http://vuln-test-site.com -t your-template.yaml -debug -debug-req- 请求的URL、头部、正文是否正确。
- 目标返回的实际状态码、响应体是什么。
- 你的
matchers逻辑是否与真实响应匹配。
- 检查匹配逻辑:在
debug输出中,找到响应体,仔细核对你的words、regex或dsl是否能够匹配上。特别注意:- 大小写:
word匹配默认区分大小写。使用case-insensitive选项或dsl的to_lower函数。 - 空格与换行:响应中的HTML或JSON格式可能包含不可见字符,影响正则匹配。
- 动态内容:如果页面包含时间戳、随机Token等每次请求都变化的内容,你的静态匹配就会失败。需要匹配那些不变的部分。
- 大小写:
- 调整请求速率:如果目标有WAF或速率限制,过快的请求可能导致被屏蔽,返回错误页面。使用
-rate-limit参数降低并发度,或添加-retries和-timeout。
5.2 误报太多?提高模板的精确度
误报浪费分析时间,降低工具可信度。
- 多用
condition: and:要求多个条件同时满足才匹配。 - 结合
status码:很多漏洞只有在返回200或特定状态码时才成立。 - 使用
dsl进行复杂判断:dsl可以计算响应长度、匹配多个正则、检查JSON路径值等,过滤能力极强。 - 添加
negative匹配器:明确排除一些会导致误报的响应特征。matchers: - type: word words: - "vulnerable string" part: body - type: word words: - "Error Page" - "Not Found" part: body negative: true # 如果包含这些词,则不算匹配 - 实战校准:将模板在大量已知安全和无漏洞的资产上运行,观察误报情况,持续优化匹配条件。
5.3 性能瓶颈与优化建议
当扫描目标成千上万时,性能至关重要。
模板优化:
- 减少不必要的请求:如果一个模板有多个
path,且它们之间是“或”的关系,Nuclei会依次请求。考虑是否可以通过一个请求和更复杂的响应匹配来判断。 - 使用
max-redirects:限制重定向次数,避免陷入无限重定向或复杂的重定向链。 - 合理设置
timeout:在模板的http块中设置适当的超时,避免在无响应的目标上等待过久。
- 减少不必要的请求:如果一个模板有多个
扫描策略优化:
- 分级扫描:先使用快速、轻量的信息收集模板(
-tags info,exposure)进行初筛,再对高风险目标进行深度漏洞扫描(-tags cve,rce)。 - 利用
-nc(无颜色)和-silent:在自动化脚本中禁用控制台颜色和实时输出,可以提升一些性能。 - 控制并发度:使用
-c(并发主机数)和-rate-limit(每秒请求数)参数,根据目标网络的承受能力进行调整,避免把对方打挂或触发安防警报。
- 分级扫描:先使用快速、轻量的信息收集模板(
资源层面:
- 升级Nuclei版本:ProjectDiscovery团队持续进行性能优化,新版本通常更快。
- 增加系统资源:对于海量目标扫描,Nuclei的并发能力受限于CPU和网络I/O。确保运行主机有足够的资源。
5.4 关于“无法更新Nuclei”或“无法更新模板”
这是一个常见的网络问题,通常是因为ProjectDiscovery的服务器位于海外,国内访问可能不稳定。
解决方案:
- 使用代理(此处需注意合规性,仅指企业内常见的网络代理服务)。可以通过设置环境变量让
go和nuclei使用代理:export HTTP_PROXY=http://your-proxy:port export HTTPS_PROXY=http://your-proxy:port nuclei -update - 手动更新:
- 更新引擎:去GitHub的 Release页面 手动下载最新版本二进制文件替换。
- 更新模板:直接进入模板目录(默认在
~/.local/nuclei-templates),执行git pull拉取最新更改。
- 使用镜像源:一些社区提供了Go模块的镜像源,但Nuclei的模板仓库更新仍需拉取GitHub,镜像源可能无法解决全部问题。
最根本的解决之道,是在内网搭建一个模板同步镜像。写一个定时任务,从GitHub拉取官方模板库更新到内部Git服务器,然后团队所有成员从内网仓库更新即可,速度又快又稳定。
