视频教程(Video tutorial)

最基础的 YAML 插件

name: poc-yaml-example-com
# 脚本部分
transport: http
rules:
    r1:
        request:
            method: GET
            path: "/"
        expression: |
            response.status==200 && response.body_string.contains("Example Domain")
expression:
    r1()
# 信息部分
detail:
    author: name(link)
    links:
        - http://example.com

整个 YAML 插件大致可以分为 3 部分:

  • 名称: 脚本名称, string 类型
  • 脚本部分: 主要逻辑控制部分,控制着脚本的运行过程
  • 信息部分: 主要是用来声明该脚本的一些信息,包括输出内容

YAML插件的脚本部分

传输方式(transport)

该字段用于指定发送数据包的协议。 transport: string

形如:

transport: http

目前 transport 的取值可以为以下 3 种之一:

  1. tcp
  2. udp
  3. http

相比于 v1 版本,v2 版本做出的一个重要变更是允许发送多种多样的数据包了,不再仅局限于 http 请求,我们新增 tcp 和 udp 的支持。

目前不允许一个脚本中发送不同种 transport 的请求,因为通常我们的输入是一个稳定的协议, 比如:

  1. 端口存活探测的结果通常会知道它是 tcp 或者 udp 存活
  2. 或者从一个明确的 http 请求或者 web 站点开始

如果后续有其它协议的需求,这个字段的取值是可以扩展的。

YAML插件主体的关键字的意义

rules:
    r0:
        request:
            cache: true
            method: POST
            path: "/"
            headers:
                Content-Type: application/json
            follow_redirects: true
            body: '{"username":"administrator","password":"1qazxsw23edcvfr4"}'
        expression: |
            response.status==200 && "welcome, administrator, xxxxxxxxxx".matches(response.body_string)
    r1:
        request:
            cache: true
            method: GET
            path: "/fetchBody?id=1/../../../../../../../../etc/passwd"
        expression: |
            response.status == 200 && "root:[x*]:0:0:".matches(response.body_string)
expression: r0() && r1()
  1. rules以及单个rule的名称

    • rules代表着一个规则集,在这个规则集中,将存放着所有要发送的信息以及要判断的规则
    • rule则是一个请求的规则,代表你想要发送什么样的请求。如上述所举的例子中,r0,r1是规则的名称
  2. request

    该关键词中存在着构建一个请求包所要填写的信息,包括请求使用的方法,请求路径,请求头,请求body,是否跟随302跳转。

    • cache: bool :是否使用缓存的请求,如果该选项为 true,那么如果在一次探测中其它脚本对相同目标发送过相同请求,那么便使用之前缓存的响应,而不发新的数据包
    • method: string :请求方法
    • path: string :请求的完整 Path,包括 querystring 等 (详情见: HTTP PATH 的使用)
      1. 如果 path 是以 / 开头的, 取 dir 路径拼接
      2. 如果 path 是以 ^ 开头的, uri 直接取该路径
    • headers: map[string]string :请求 HTTP 头,Rule 中指定的值会被覆盖到原始数据包的 HTTP 头中
    • body: string :请求的Body(转译问题见:头疼的转译
    • follow_redirects: bool :是否允许跟随300跳转, method为GET或者HEAD时,该选项为true时,其他情况为false。
  3. expression

    在rule下的expression是用来对返回包(response)进行匹配的,你可以编写各种各样的限制来判断返回包中信息,从而确认返回的内容是否符合要求。 正如spring使用SpEL表达式,struts2使用OGNL表达式,xray使用了编译性语言Golang,所以为了实现动态执行一些规则,我们使用了Common Expression Language (CEL)表达式:

    response.status==200 && response.body_string.contains("Example Domain")
    

    CEL表达式通熟易懂,非常类似于一个Python表达式。上述表达式的意思是:返回包的status等于200,且body中包含内容“Example Domain”。 expression表达式上下文还包含有一些常用的函数。比如上述 bcontains 用来匹配 bytes 是否包含,类似的,如果要匹配 string 的包含,可以使用 contains, 如: 值得注意的是,类似于python,CEL中的字符串可以有转义和前缀,如:(详情见:头疼的转义)

    • '\r\n' 表示换行
    • r'\r\n' 不表示换行,仅仅表示这4个字符。在编写正则时很有意义。
    • b'test' 一个字节流(bytes),在golang中即为[]byte

    用一些简单的例子来解释大部分我们可能用到的表达式:

    每一个expression表达式都会返回一个bool结果,然后在存在&&||的时候,与其他的expression表达式做运算,最终出一个结果,最终结果如果为true,则代表这个规则命中。

与rules同级的expression的使用

对于脚本层级的 expression,这个结果作为最后脚本是否匹配成功的值,通常脚本层级的 expression 是 rule 结果的一个组合。 比如一个脚本包含 r1, r2, r3r4 4 条规则, 作为脚本层级的 expression,其全局变量将会定义 r1, r2, r3r4 4 个函数,调用这个 4 个函数即可获得它对应 rule 的结果。

expression: r1() && r2() && r3() && r4()

相比于 V1 版本, rule 如何运行这件事情发生了较大的改变。假设我们有 r1, r2, r3r4 4 条规则

最开始时, V1 版本添加了 rules: []Rule 来定义 rule 及其执行顺序。其逻辑为顺序执行 rule, 且 r1, r2, r3r4 都为 true 时, 脚本执行成功

rules:
    r1: ...
    r2: ...
    r3: ...
    r4: ...

然后添加了 group: map[string][]Rule 拓展了一下 ruel 的执行方式。支持了 r1,r2 同时为 true 或者 r3, r4 同时为 true 时, 脚本执行成功

group:
    g1: [r1, r2]
    g2: [r3, r4]

在 V2 版本下, 我们使用 expression 来组织 rule 的执行逻辑。

对于方式1,其对应的形式为:

rules:
    r1: ...
    r2: ...
    r3: ...
    r4: ...
expression: r1() && r2() && r3() && r4()

对于方式2, 其对应的形式为:

rules:
    r1: ...
    r2: ...
    r3: ...
    r4: ...
expression: (r1() && r2()) || (r3() && r4())

甚至我们还能支持:

rules:
    r1: ...
    r2: ...
    r3: ...
    r4: ...
expression: (r1() || r2() || r3()) && r4()

这里有几点需要说明一下:

  1. rule 的执行顺序是按照该逻辑表达式的执行顺序来执行的
  2. 短路求值, 即 r1() || r2(), 如果 r1() 的结果为 true 那么 r2 是不会执行的

set关键字的使用

todo:补充高级用法链接 该字段主要是用来定义一些在接下来的规则中会使用到的一些全局变量,比如随机数,反连平台等。 set: map[string]interface{}

set:
    a: 1
    num: randonInt(1000, 2000)      # 1543
    rstr: randomLowercase(10)       # asdbeuyekp
    reverse: newReverse()
    reverseURL: reverse.url

payload关键词的使用

todo:补充高级用法链接 该字段用于定义多个 payload,来实现发送不同 payload 的效果。 该字段结构如下

变量名/函数名类型说明
continuebool命中一个之后是否继续
payloadsmap[string]Setset一样的结构和语法

形如:

payloads:
  continue: false
  payloads:
    ping:
      cmd: r"ping test.com"
    curl:
      cmd: r"curl test.com"

:

  1. 只支持罗列 payload, 目前不考虑支持文件或者复杂排列组合等情况,每个 payload 中的 key 必须严格一致
  2. 循环 payload 然后把当前 payload 加在 set 之后,组成新的 set 执行
set:
    a: 1
payloads:
    - b: a
    - b: b

实际运行结果相当于,运行两遍

第一遍:

set:
    a: 1
    b: a

第二遍:

set:
    a: 1
    b: b

todo:补充高级用法链接

  1. 获取返回的token
  2. 获取上传文件后返回的文件路径
  3. 总结:获取所需要的参数

返回包

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8;

{"pbx":"COMpact 4000","pbxType":20,"pbxId":0,"serial":"4107646840","date":"05.07.2022","macaddr":"00:01:01:01:01:01"}

匹配

rules:
    r1:
        request:
            method: GET
            path: "/about_state"
        expression: response.status == 200 && "serial.*?\d+.,".matches(response.body_string) && "date.+?20[0-9][0-9].,".matches(response.body_string)
        output:
            search: '"serial\":\"(?P<serial>.+?)\",\"date".bsubmatch(response.body)'
            serial: search["serial"]        // 4107646840
            search1: '"date\":\"(?P<date>.+?)\",\"macaddr".bsubmatch(response.body)'
            date: search1["date"]           // 05.07.2022
            HA: serial + "r2d2" + date      // 4107646840r2d205.07.2022
            password: substr(md5(HA),0,7)   // e3048ad

YAML插件的信息部分

todo:补充高级用法链接 该字段用于定义一些和脚本相关的信息。

目前主要定义了一下几个部分:

:其中支持变量渲染,形如 {{version}}, 其中变量为 set 或者 rule output 中定义的变量

detail:
  author: # 作者(个人主页)
  links:
    - # 参考链接
    - # 可以是多个链接
  warning: # 一些警告信息,也就是该poc可能会产生的问题,比如产生脏数据,创建了一个临时文件无法删除,创建了一个账户等
  description: # 对该poc/漏洞的描述
  fingerprint: # 当该插件为指纹插件时才需要填写,且一般会自动生成
    id: # 指纹库id,一般自动生成,可以不用填写
    name: # 通用名称,一般自动生成,可以不用填写
    version: '{{version}}' # 用来接收指纹插件匹配到的版本信息,一般直接用这样的固定格式即可,会自动将output中匹配到的内容渲染过来
    cpe: # 输出该指纹对应的产品的cpe,一般自动生成,可以不用填写
    xxxx: # 一些希望被输出的内容,输出时,xxxx就是字段名称,后面就是输出值
  vulnerability: # 当该插件为漏洞插件且需要输出一些内容时才需要填写,且一般会自动生成
    id: # 漏洞库id,一般自动生成,可以不用填写
    level: # 漏洞危害等级,一般自动根据漏洞信息生成,可以不用填写
    match: # 一些证明漏洞存在的信息,比如信息泄露泄露的一些敏感数据等
    xxxx: # 一些希望被输出的内容,输出时,xxxx就是字段名称,后面就是输出值