脚本编写

YAML

一种可读的序列化数据,类似JSON。参考:YAML - Wiki

基本信息

  • 文件命名格式为:组件-编号-漏洞类型.yml,如:node-cve-2017-14849-fileread.yml
# 基本信息
# POC名称,一般格式为 poc-yaml-<组件名>-<漏洞编号>-<漏洞类型>
name: poc-yaml-test
# 区分是否手工编写,Xray有一些poc是自动生成的
manual: true

脚本部分

  • set:定义全局变量

    • 随机整数:变量名: randomInt(min, max)
    • 随机字符:变量名: randomLowercase(length)
  • transport:通信协议,tcp/udp/http
  • rules:语法规则

    • request字段:定义请求方式和目标路径
    • expression字段:判断规则是否命中,返回true/false
    • out字段:可以从响应包中获取数据

      • search字段定义匹配的正则表达式,返回一个字典
      • info:search["info"]

        • info是自定义的变量名,后面可以用{{info}}进行调
        • search["info"]search字典中Key为info的值
  • expression:全部rule的执行顺序,遵循短路求值

    • 短路求值:即r1() || r2(),如果r1()的结果为true,那么r2()不会执行
    • 示例:

      • r1() && r2() && r3(),全部规则命中时返回true
      • r1() || r2() || r3(),任一规则命中时返回true
      • r1() || (r2() && r3())r1规则命中,或者r2、r3规则同时命中时返回true
# 脚本部分
# 全局变量
set:
    # 范围随机整数/字符
    randInt0: randomInt(1000, 9999)
    randStr1: randomLowercase(10) 

# 通信协议
transport: http     

# 匹配规则
rules:
    r1:
        # 请求方式
        request:
            method: GET
            path: "/"

        # 最终执行结果
        expression: |
            response.status == 200 && response.body.bcontains(b"example") 
          
        # 从响应包获取数据
        output:  
            # search,指定搜索语法
            search: |
                r'(?P<info>\|.*\|)'.bsubmatch(response.raw)'
            # 变量名:匹配规则
            info: search["info"]

# rule执行顺序
expression:
    r1()

信息部分

  • 非必填内容
# 信息部分
detail:
  author: Chaitin(https://www.chaitin.cn/)
  links:
    - https://docs.xray.cool/
  # 还有一些指纹和漏洞信息,可以参考文档
  • 完整POC
# 基本信息
# POC名称,一般格式为 poc-yaml-[框架名]-<漏洞编号>
name: poc-yaml-test
# 区分是否手工编写,Xray有一些poc是自动生成的
manual: true

# 脚本部分
# 全局变量
set:
    # 范围随机整数/字符
    randInt0: randomInt(1000, 9999)
    randStr1: randomLowercase(10) 

# 通信协议
transport: http     

# 匹配规则
rules:
    r1:
        # 请求方式
        request:
            method: GET
            path: "/"

        # 最终执行结果
        expression: |
            response.status == 200 && response.body.bcontains(b"example") 
          
        # 从响应包获取数据
        output:  
            # search,指定搜索语法
            search: |
                r'(?P<info>\|.*\|)'.bsubmatch(response.raw)'
            # 变量名:匹配规则
            info: search["info"]

# rule执行顺序
expression:
    r1()

# 信息部分,非必填内容
detail:
  author: Chaitin(https://chaitin.com/)
  links:
    - https://docs.xray.cool/
  # 还有一些指纹和漏洞信息,可以参考文档

expression

  • 匹配响应包
expression: response.status == 200                    # Status-Code
expression: "zbx_session" in response.headers         # Header
expression: response.body.bcontains(b"verify_string") # Body

# 前面使用了md5加密随机值: md5({{randNum}}), 后面可使用以下语句来进行匹配: 
expression: response.body.bcontains(bytes(md5(string(randNum))))
  • 搜索
# 搜索Body
search: |
  "\"verify_string\":\"(?P<token>\\w+)\"".bsubmatch(response.body) 

常用字段

output - rule完成后的全局变量

定义了这条 rule 运行完成之后的一些变量,该字段定义的变量会被添加到全局变量。
  • out字段下的变量是全局变量,如果2条rule里在匹配正则时,都用到search变量,后面的search的内容会和前面的search一样,也就是说第2条正则不会生效,所以后面info2自然和前面info1一样。不会覆盖也不会报错,第一次遇到的时候排查了很久,记录一下
# 错误示例

r0:
    request:
    method: GET
    path: /
    expression: response.status == 200 
    output:
        search: "?P<info>\\w+".bsubmatch(response.body) 
        info1: search["info"]
r1:
    request:
        method: GET
        path: /test.php
    expression: response.status == 200
    output:
        search: "?P<info>\\w+".bsubmatch(response.body) 
        info2: search["info"]
  • 正确写法应该是命名不同的变量
# 正确示例

r0:
    request:
    method: GET
    path: /
    expression: response.status == 200 
    output:
        r0search: "?P<info>\\w+".bsubmatch(response.body) 
        info1: r0search["info"]
r1:
    request:
        method: GET
        path: /test.php
    expression: response.status == 200
    output:
        r1search: "?P<info>\\w+".bsubmatch(response.body) 
        info2: r1search["info"]

follow_redirects - 跟随跳转

  • 可以通过设置follow_redirects: bool来判断是否允许跟随30X跳转
  • 举例场景:

    • 如果需要从一个302跳转的包中取值并设置到Cookie中,这个时候就需要显式设置follow_redirects为´false,然后再手动发起一次新的请求包。否则如果跟随跳转的话,则会携带原来Cookie

payload - 全局变量载荷

该字段用于定义多个 payload,来实现发送不同 payload 的效果。
  • 这个字段是POCv2版本新增的,Gamma语法检查时会报错,但是实际可以运行
变量名/函数名类型说明
continuebool命中一个之后是否继续,默认false命中即停
payloadsmap[string]Set和 set 一样的结构和语法
  • 每个 payload 中的 key 必须严格一致
payloads:
  payloads:
    ping:
      cmd: r"ping test.com"
    curl:
      cmd: r"curl test.com"

reverse - 反连平台

  • 设变量名为reverse,需要先使用newReverse()生成实例)
变量名/函数名类型说明
reverse.urlurlType反连平台的 url
reverse.domainstring反连平台的域名
reverse.ipstring反连平台的 ip 地址
reverse.is_domain_name_serverbool反连平台的 domain 是否同时是 nameserver
reverse.wait(timeout)func (timeout int) bool等待 timeout 秒,并返回是否在改时间内获得了信息
set:
  reverse: newReverse()
  reverseURL: reverse.url

rules:
  r1:
    request:
      method: POST
      path: "/xxx/{{reverseURL}}"
      expression: |
        reverse.wait(5)

参考知识

CEL

脚本调试

  • Xray格式检测:使用以下命令进行格式检测,格式通过才可以在社区进行提交,否则会报错提示“POC代码错误,建议使用Xray进行格式检测”
$ xray poclint --script poc-yaml-xxx.yml
  • Xray调试:先在config.yaml中配置proxy,可以通过Burp查看数据包
$ xray --log-level debug webscan --url http://example.com -p ./poc-yaml-xxx.yml
  

Xray调试

  • Gamma:提供YAML脚本运行环境,请求响应会以Base64编码形式返回,可以使用--http-proxy参数代理到Burp中
# 语法检查
$ gamma lint --script xxx.yml

# 运行
$ gamma run --target "http://xxx.com" --script xxx.yml

# 调试运行
$ GAMMA_LOG_LEVEL=debug ./gamma run --no-cache --target "http://xxx.com" --script xxx.yml

# 搭配Burp调试
$ gamma run --target "http://xxx.com" --script xxx.yml --http-proxy http://127.0.0.1:8080

Gamma调试

Gamma运行

如果觉得我的文章对你有帮助,请我吃颗糖吧~