跳转到内容
Go back

Nuclei 模板编写笔记

更新于:

Nuclei 模板编写笔记

快速编写模版流程

辅助插件

具体步骤

  1. 右键将数据包发送到 nuclei 插件,选择创建模板或者追加到模板
  2. 再使用 Nu_Te_Gen 插件生成合适的匹配器,或者查询内置函数文档

模板命名规范

调试命令

常用模板调试相关参数

# 详细输出
nuclei -t template.yaml -u target.com -v

# 调试模式
nuclei -t template.yaml -u target.com -debug

# 显示请求/响应
nuclei -t template.yaml -u target.com -debug-req -debug-resp

# 验证模板语法
nuclei -t template.yaml -validate

# 验证并显示详细信息
nuclei -t template.yaml -validate -v

在编写模板后,务必进行语法验证:

# 验证单个模板
nuclei -t my-template.yaml -validate

# 验证目录中的所有模板
nuclei -t templates/ -validate

# 验证并显示警告
nuclei -t template.yaml -validate -v

常见验证错误:

模板签名

为确保模板完整性,可以对模板进行数字签名。

生成密钥对

# 生成私钥
openssl genrsa -out private-key.pem 2048

# 生成公钥
openssl rsa -in private-key.pem -pubout -out public-key.pem

签名模板

nuclei -t template.yaml -sign -private-key private-key.pem

验证签名

nuclei -t template.yaml -verify -public-key public-key.pem

Burp-nuclei 快捷键备忘

快捷键

Nuclei Template

Nuclei Template 基本结构示例:

id: template-id                    # 唯一标识符,不能包含空格

info:
  name: Template Name              # 模板名称
  author: author-name              # 作者
  severity: medium                 # 严重程度:info, low, medium, high, critical
  description: Template description # 描述
  reference:                       # 参考链接
    - https://example.com
  tags: tag1,tag2                  # 标签,用于分类
  metadata:                        # 元数据
    shodan-query: 'vuln:CVE-2021-26855'

variables:                         # 模板级变量
  var1: "value"
  var2: "{{base64('hello')}}"

http:                             # 协议请求
  - method: GET
    path:
      - "{{BaseURL}}/path"
    headers:
      User-Agent: Custom-Agent
    
    matchers:                     # 匹配器
      - type: word
        words:
          - "success"
    
    extractors:                   # 提取器
      - type: regex
        regex:
          - "token=([a-zA-Z0-9]+)"

内置变量

变量描述示例
{{BaseURL}}完整的基础 URLhttps://example.com:443/foo/bar.php
{{RootURL}}根 URLhttps://example.com:443
{{Hostname}}主机名和端口example.com:443
{{Host}}主机名example.com
{{Port}}端口443
{{Path}}路径/foo
{{File}}文件名bar.php
{{Scheme}}协议https
{{FQDN}}完全限定域名example.com

特殊参数备忘

使用 stop-at-first-match 选项获得匹配响应也要继续执行。

    stop-at-first-match: false
    matchers:
      - type: dsl
        dsl:
          - "len(body) > 109"

较新版本的 nuclei 中已经默认启用同一模板中多个会话中传递 cookie,使用 disable-cookie: true 可以禁用这一默认行为:

http:
  - method: GET
    path:
      - "{{BaseURL}}/login"
    # disable-cookie: true  # 禁用 Cookie 会话管理

重定向控制

http:
  - method: GET
    path:
      - "{{BaseURL}}/redirect"
    redirects: true        # 启用重定向跟踪
    max-redirects: 5       # 最大重定向次数

参数备忘

攻击模式的总结如下:

模式描述
Batteringram使用一个载荷列表,遍历载荷并将相同的载荷注入请求中定义的所有注入点。
Pitchfork为每个注入点使用一个单独的载荷列表,并并行遍历这些载荷列表。
Clusterbomb接收多个载荷列表,并将在每个注入点中迭代每个列表的载荷的所有排列组合。

多请求

http:
  - method: GET
    path:
      - "{{BaseURL}}/login"
    extractors:
      - type: regex
        name: csrf_token
        part: body
        internal: true
        regex:
          - 'name="csrf_token" value="([^"]+)"'

  - method: POST
    path:
      - "{{BaseURL}}/login"
    headers:
      Content-Type: application/x-www-form-urlencoded
    body: |
      username=admin&password=admin&csrf_token={{csrf_token}}
    
    matchers:
      - type: word
        words:
          - "Welcome"

条件竞争

要在模板中启用竞态条件检查,请设置选项 race: true 并将 race_count 值设置为要发送的并发请求数量。

Flow 流程控制

Flow 是 Nuclei v3 引入的强大功能,提供条件执行和请求编排能力。

条件执行

id: wordpress-bruteforce

info:
  name: WordPress Login Bruteforce
  author: pdteam
  severity: high

flow: http(1) && http(2)  # 先执行第一个请求,成功后执行第二个

http:
  - method: GET
    path:
      - "{{BaseURL}}/wp-login.php"
    matchers:
      - type: word
        words:
          - "WordPress"

  - method: POST
    path:
      - "{{BaseURL}}/wp-login.php"
    body: |
      log={{username}}&pwd={{password}}&wp-submit=Log+In
    attack: clusterbomb
    payloads:
      users: helpers/wordlists/wp-users.txt
      passwords: helpers/wordlists/wp-passwords.txt

请求编排

使用 JavaScript (ECMAScript 5.1) 进行复杂的流程控制:

id: vhost-enum-flow

info:
  name: vhost enum flow
  author: tarunKoyalwar
  severity: info

flow: |
  ssl();
  for (let vhost of iterate(template["ssl_domains"])) {
    set("vhost", vhost);
    http();
  }

ssl:
  - address: "{{Host}}:{{Port}}"

http:
  - raw:
      - |
        GET / HTTP/1.1
        Host: {{vhost}}
    matchers:
      - type: dsl
        dsl:
          - status_code != 400
          - status_code != 502

Matchers 匹配器

匹配二进制与十六进制响应

matchers:
  - type: binary
    binary:
      - "504B0304" # zip archive
      - "526172211A070100" # RAR archive version 5.0
      - "FD377A585A0000" # xz tar.xz archive
    condition: or
    part: body

匹配二进制与十六进制响应

matchers:
  - type: word
    encoding: hex
    words:
      - "50494e47"
    part: body

Conditions 条件

单个匹配器中可以指定多个单词和正则表达式,并可以配置不同的条件,如 AND 和 OR。如果没有指定条件,则默认使用 OR。

Negative Matchers 负向匹配器

所有类型的匹配器也支持负向条件,这在你寻找具有排除条件的匹配时非常有用。这可以通过在匹配器块中添加 negative: true 来使用。

这里是一个使用 negative 条件的示例语法,这将返回所有在响应头中不包含 PHPSESSID 的 URL。

matchers:
  - type: word
    words:
      - "PHPSESSID"
    part: header
    negative: true

Internal Matchers 内部匹配器

在多协议或 Flow 模板中,使用 internal: true 可以跳过中间结果的输出,只显示最终结果。

使用 internal: true 跳过中间结果的输出:

http:
  - method: GET
    path:
      - "{{BaseURL}}/wp-content/plugins/backup-backup/readme.txt"
    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'contains(body, "Backup Migration")'
        condition: and
        internal: true  # 不输出此匹配结果

  - method: POST
    path:
      - "{{BaseURL}}/wp-content/plugins/backup-backup/includes/backup-heart.php"
    matchers:
      - type: dsl
        dsl:
          - 'len(body) == 0'
          - 'status_code == 200'
        condition: and

在编写多协议或基于 flow 的模板时,可能存在需要先验证/匹配第一个请求,然后才继续下一个请求的情况,一个很好的例子就是 CVE-2023-6553

在这个模板中,我们首先使用 matchers 和 Backup Migration 插件检查目标是否真实,如果为真,则使用 flow 继续下一个请求

但是这将打印两个结果,每个请求匹配一个结果,因为我们使用第一个请求匹配器作为进入下一个请求的先决条件,我们可以使用匹配器块中的 internal: true 将其标记为内部。

id: CVE-2023-6553

info:
  name: Worpress Backup Migration <= 1.3.7 - Unauthenticated Remote Code Execution
  author: FLX
  severity: critical

flow: http(1) && http(2)

http:
  - method: GET
    path:
      - "{{BaseURL}}/wp-content/plugins/backup-backup/readme.txt"

    matchers:
      - type: dsl
        dsl:
          - 'status_code == 200'
          - 'contains(body, "Backup Migration")'
        condition: and
        internal: true  # <- updated logic (this will skip printing this event/result)

  - method: POST
    path:
      - "{{BaseURL}}/wp-content/plugins/backup-backup/includes/backup-heart.php"
    headers:
      Content-Dir: "{{rand_text_alpha(10)}}"

    matchers:
      - type: dsl
        dsl:
          - 'len(body) == 0'
          - 'status_code == 200'
          - '!contains(body, "Incorrect parameters")'
        condition: and

Extractors 提取器

一个 kval Extractor 示例,用于从 HTTP 响应中提取 content-type 头部。

请注意, content-type 已被替换为 content_type ,因为 kval Extractor 不接受连字符 ( - ) 作为输入,必须用下划线 ( _ ) 替换。

extractors:
  - type: kval # type of the extractor
    kval:
      - content_type # header/cookie value to extract from response

Dynamic Extractor 动态提取器

此功能仅在 RAW 请求格式中可用。

extractors:
      - type: regex
        name: api
        part: body
        internal: true # Required for using dynamic variables
        regex:
          - "(?m)[0-9]{3,10}\\.[0-9]+"

使用 Nuclei v3.1.4,您现在可以立即在后续的提取器中重用动态提取的值(例如:上述示例中的 csrf_token),并且默认在后续请求中可用

id: basic-raw-example

info:
  name: Test RAW Template
  author: pdteam
  severity: info


http:
  - raw:
      - |
        GET / HTTP/1.1
        Host: {{Hostname}}

    extractors:
      - type: regex
        name: title
        group: 1
        regex:
          - '<title>(.*)<\/title>'
        internal: true

      - type: dsl
        dsl:
          - '"Title is " + title'

Flow

模板流程引擎在 Nuclei v3 中被引入,为 Nuclei 带来了两个重要改进:

在编写复杂模板时,我们经常需要在执行请求的某些部分之前添加一些额外的检查(或条件语句)。

flow 接受任何 JavaScript(ECMAScript 5.1)表达式/代码,因此你可以自由地构建任何你想要的条件执行逻辑。

这是一个理想的例子,比如当我们使用默认的用户名和密码暴力破解 WordPress 登录时,但如果我们仔细重新评估这个模板,我们可以看到这个模板在不检查 URL 是否实际存在或目标站点是否真的是 WordPress 站点的情况下发送了 276 个请求。

通过在 Nuclei v3 中添加流程,我们可以重写这个模板,首先检查目标是否是 WordPress 站点,如果是,则使用默认凭据暴力破解登录,这可以通过简单地添加一行内容即可实现,即 flow: http(1) && http(2) ,Nuclei 将处理所有其他事情。

id: wordpress-bruteforce

info:
  name: WordPress Login Bruteforce
  author: pdteam
  severity: high

flow: http(1) && http(2)

http:
  - method: GET
    path:
      - "{{BaseURL}}/wp-login.php"

    matchers:
      - type: word
        words:
          - "WordPress"

  - method: POST
    path:
      - "{{BaseURL}}/wp-login.php"

    body: |
        log={{username}}&pwd={{password}}&wp-submit=Log+In

    attack: clusterbomb 
    payloads:
      users: helpers/wordlists/wp-users.txt
      passwords: helpers/wordlists/wp-passwords.txt

    matchers:
      - type: dsl
        dsl:
          - status_code == 302
          - contains_all(header, "/wp-admin","wordpress_logged_in")
        condition: and

其他技巧

查找 XPath 表达式的一个快速技巧是使用浏览器开发工具来显示给定元素的节点层次结构。通过在浏览器中打开开发工具并使用检查器获取页面元素的信息,工具窗口应显示选中节点的路径。例如,在 Firefox 中,状态栏区域会显示选中元素的路由信息。

DAST Nuclei 模板

Nuclei 支持基于规则的 HTTP 请求模糊测试,可以创建通用 Web 应用程序漏洞模板(如 SQLi、SSRF、CMDi 等),无需了解目标的具体信息。

DAST 模板可以在主机上执行命令,并且默认扫描不包括这些模板。要使用这些模板,您可以通过提供 -dast 标志来运行它们。

Fuzzing 模板基本结构

http:
  - pre-condition:  # 预条件,决定何时执行模糊测试
      - type: dsl
        dsl:
          - 'method == "GET"'
          - 'len(body) > 0'
        condition: and
    
    payloads:
      reflection:
        - "6842'\"><9967"
    
    fuzzing:
      - part: query      # 模糊测试的部分
        type: postfix    # 替换类型
        mode: single     # 模式
        fuzz:
          - "{{reflection}}"
    
    matchers:
      - type: word
        part: body
        words:
          - "{{reflection}}"

模糊测试部分 (Part)

替换类型 (Type)

模式 (Mode)

组件数据过滤

fuzzing:
  - part: query
    type: replace
    mode: single
    keys:           # 精确匹配参数名
      - "daemon"
      - "cmd"
    keys-regex:     # 正则匹配参数名
      - "redirect.*"
    values:         # 正则匹配参数值
      - "https?://.*"

分析器 (Analyzer)

时间延迟分析器

用于验证时间盲注等漏洞:

analyzer:
  name: time_delay
  parameters:
    sleep_duration: 10
    requests_limit: 6
    time_correlation_error_range: 0.30
    time_slope_error_range: 0.40

fuzzing:
  - part: request
    type: postfix
    mode: single
    fuzz:
      - " and sleep([SLEEPTIME]) -- "

matchers:
  - type: word
    part: analyzer
    words:
      - "true"

动态占位符:

常见模糊测试模板示例

XSS 检测

id: fuzz-reflection-xss

info:
  name: Basic Reflection Potential XSS Detection
  author: pdteam
  severity: low

http:
  - pre-condition:
      - type: dsl
        dsl:
          - 'method == "GET"'
    
    payloads:
      reflection:
        - "6842'\"><9967"
    
    fuzzing:
      - part: query
        type: postfix
        mode: single
        fuzz:
          - "{{reflection}}"
    
    matchers-condition: and
    matchers:
      - type: word
        part: body
        words:
          - "{{reflection}}"
      - type: word
        part: header
        words:
          - "text/html"

SSTI 检测

variables:
  first: "{{rand_int(10000, 99999)}}"
  second: "{{rand_int(10000, 99999)}}"
  result: "{{to_number(first)*to_number(second)}}"

http:
  - payloads:
      reflection:
        - '{{concat("{{", "§first§*§second§", "}}")}}'
    
    fuzzing:
      - part: query
        type: postfix
        mode: multiple
        fuzz:
          - "{{reflection}}"
    
    matchers:
      - type: word
        part: body
        words:
          - "{{result}}"

盲注 SSRF 检测

http:
  - payloads:
      redirect:
        - "{{interactsh-url}}"
    
    fuzzing:
      - part: query
        type: replace
        mode: single
        keys:
          - "dest"
          - "redirect"
          - "url"
        fuzz:
          - "https://{{redirect}}"
    
    matchers:
      - type: word
        part: interactsh_protocol
        words:
          - "http"

在编写/执行模板时,可以使用 -v -svd 标志在应用过滤器之前查看过滤器中所有可用的变量。

文件模式

使用命令示例:

nuclei -file -target /path/to/folder/containing/subject/files/ -t google-api-key.yaml
  1. 文件模式支持 extractors 提取器matchers 匹配器
  2. 文件模式仅支持 wordregex 提取器类型。这些行为方式与其他模式中所述相同。

文件模式模板:

应向模板提供要处理的文件扩展名列表 (extensions)。 all 扩展名基本上是一个通配符,匹配除可选拒绝列表中提供的那些扩展名之外的所有扩展名。

id: google-api-key

info:
  name: Google API Key
  author: pdteam
  severity: info

file:
  - extensions:      # 文件扩展名
      - all          # 匹配所有扩展名(除默认黑名单外)
      - txt          # 或指定特定扩展名

  - denylist:        # 黑名单扩展名
	  - go
	  - py
	  - txt

    max-size: 5242880  # 最大文件大小(字节),默认 5MB
    no-recursive: false # 是否禁用递归遍历
    
    extractors:
      - type: regex
        name: google-api-key
        regex:
          - "AIza[0-9A-Za-z\\-_]{35}"

默认情况下,nuclei 文件模块中会排除某些扩展名。这些扩展名的列表如下

3g2,3gp,7z,apk,arj,avi,axd,bmp,css,csv,deb,dll,doc,drv,eot,exe,flv,gif,gifv,gz,h264,ico,iso,jar,jpeg,jpg,lock,m4a,m4v,map,mkv,mov,mp3,mp4,mpeg,mpg,msi,ogg,ogm,ogv,otf,pdf,pkg,png,ppt,psd,rar,rm,rpm,svg,swf,sys,tar,tar.gz,tif,tiff,ttf,txt,vob,wav,webm,wmv,woff,woff2,xcf,xls,xlsx,zip

更多选项

Workflows 工作流

条件工作流的核心要点是:

条件工作流定义的基本组成部分是:

以下工作流不使用任何匹配器,只有当模板有任何发现时才会运行子模板:

workflows:
  - template: technologies/jira-detect.yaml
    subtemplates:
      - tags: jira
      - template: exploits/jira/

以下工作流程使用匹配器,并在运行各种 WordPress 模板之前,会检查 tech-detect 模板的输出是否包含字符串“wordpress”:

workflows:
  - template: technologies/tech-detect.yaml
    matchers:
      - name: wordpress
        subtemplates:
          - template: cves/CVE-2019-6715.yaml
          - template: cves/CVE-2019-9978.yaml
          - template: files/wordpress-db-backup.yaml
          - template: files/wordpress-debug-log.yaml

条件工作流可以串联多个条件,以执行复杂的模板序列。一个条件工作流模板的示例是:

workflows:
  - template: technologies/tech-detect.yaml
    matchers:
      - name: foo-xyz
        subtemplates:
          - template: technologies/foo-xyz-version-3.yaml
            subtemplates:
              - template: cves/2022/CVE-2022-123456.yaml
                subtemplates:
                  - template: cves/CVE-2022-123457.yaml

这个假设性示例仅在满足以下条件时才会运行 CVE-2022-123457 模板:

该 tech-detect 模板检测 foo-xyz,并且 foo-xyz 的版本是 3.x,同时它检测到 CVE-2022-123456。

因此,总的来说,工作流提供了定义更高效和更有针对性的扫描步骤以保存和共享的能力。

全局匹配器

global-matchers 自 Nuclei v3.3.5 起可用,可以参考文档了解更多关于其用法的信息。

使用命令示例:

必须使用 -enable-global-matchers/-egm 参数才能启用全局匹配器

nuclei -enable-global-matchers \
  -t http-template-with-global-matchers.yaml \
  -t http-template-1.yaml \
  -t http-template-2.yaml \
  -silent -u http://scanme.sh

全局匹配器模版示例:

该模板设置了 global-matchers: true,这表示告知 Nuclei 在扫描过程中对所有 HTTP 响应应用这些匹配器。而 matchers-condition: or 意味着只要发现 任意 一个定义的模式匹配,就会标记该响应。

# http-template-with-global-matchers.yaml
http:
  - global-matchers: true
    matchers-condition: or
    matchers:
      - type: regex
        name: asymmetric_private_key
        regex:
          - '-----BEGIN ((EC|PGP|DSA|RSA|OPENSSH) )?PRIVATE KEY( BLOCK)?-----'
        part: body

      - type: regex
        name: slack_webhook
        regex:
          - >-
            https://hooks.slack.com/services/T[a-zA-Z0-9_]{8,10}/B[a-zA-Z0-9_]{8,12}/[a-zA-Z0-9_]{23,24}
        part: body

模板小抄

构造 Jwt

构造 jwt 的 python 伪代码

def generate_jwt_token():
    """Generate JWT token"""
    # Get current timestamp
    current_time = int(datetime.now().timestamp())
    
    payload = {
        "aud": "zplatUsers",
        "exp": current_time + 3600,  # Expires in 1 hour
        "iat": current_time,          # Issued at current time
        "iss": "zplat",
        "nbf": current_time,          # Not before current time
        "sub": "1",
        "roles": [
            "admin"
        ]
    }
    
    secret = "123"
    token = jwt.encode(payload, secret, algorithm='HS512')
    return token

伪代码对照的 nuclei 模板变量示例

variables:
  json: |
    {
      "aud": "zplatUsers",
      "iss": "zplat",
      "sub": "1",
      "roles": ["admin"]
    }
  secret: "123"
  algorithm: "HS512"
  # 总是设置为当前时间后24小时
  maxAge: '{{unix_time() + 3600}}'  # 3600秒 = 1小时
  jwt: '{{generate_jwt(json, algorithm, secret, maxAge)}}'

关键点:

dsl 匹配器提取 json 字段

    extractors:
      - type: json
        json:
          - '.data | {bucket: .bucket, endpoint: .endpoint, access_key: .access_key, secret_key: .secret_key}'
        name: storage_config

使用 flow 来控制多数据包执行流程

# 使用Flow来控制执行流程
flow: |
  http(1);
  for (let storage_id of iterate(template["storage_ids"])) {
    set("storage_id", storage_id);
    http(2);
  }

http 爆破

http:
  - raw:
      - |-
        POST /{{Path}} HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json
        
        {"username":{{username}},"password":{{password}}}

    attack: clusterbomb   # 可用类型: batteringram,pitchfork,clusterbomb
    payloads:
      username:
        - 'admin'
      password:
        - 'admin'
      Path: 
        - 'api/selectContentManagePage'
      # header: helpers/wordlists/header.txt

dsl 匹配器

印象中,匹配 http header 时,需要将 - 替换成 _

    matchers:
      - type: dsl
        dsl:
          - "status_code == 200"
          - "contains_all(body_1, 'pageSize', 'pageNum')" 
          - "contains(content_type, 'application/json')"
        condition: and

从开源模板中学到的邪教写法,在 dsl 匹配器中内嵌正则

    matchers:
      - type: dsl
        dsl:
            - "contains(interactsh_protocol, 'http')"
            - "contains(content_type, 'text/plain')"
            - "regex('^{{string}}$', body)"
            - "status_code == 200"
        condition: and

windows 任意文件读取

    matchers:
      - type: dsl
        dsl:
          - "status_code == 200"
          - "contains_all(body, 'bit app support', 'fonts', 'extensions')"
          - "len(body_1) <= 1024"
        condition: and

linux 任意文件读取

    matchers-condition: and
    matchers:
      - type: regex
        regex:
          - "root:[x*]:0:0:"
        part: body
      - 
      - type: dsl
        dsl:
          - "status_code == 200"
          - "len(body_1) <= 1024"
        condition: and

二进制流匹配

matchers:
  - type: binary
    binary:
      - "504B0304" # zip archive
      - "526172211A070100" # RAR archive version 5.0
      - "FD377A585A0000" # xz tar.xz archive
    condition: or
    part: body

正则匹配邮箱

    extractors:
      - type: regex
        part: body
        regex:
          - "[a-zA-Z0-9-_.]{4,}@[A-Za-z0-9_-]+[.](com|org|net|io|gov|co|co.uk|com.mx|com.br|com.sv|co.cr|com.gt|com.hn|com.ni|com.au|com.cn|cn|gov.cn)"

URL 重定向

    matchers:
      - type: regex
        part: header
        regex:
          - "(?m)^(?:Location\\s*:\\s*)(?:https?://|//|\\\\)?(?:[a-zA-Z0-9\\-_]*\\.)?interact\\.sh(?:\\s*)$"

参考资料


分享文章至:

Previous Post
Mac 下查看端口占用情况
Next Post
博客随记