作者:swing
本文为作者投稿,Seebug Paper 期待你的分享,凡经采用即有礼品相送! 投稿邮箱:[email protected]
这两天和 @leommxj 一起分析了和写了一下 CVE-2023-27997 的漏洞利用, 顺便一想想 CVE-2022-42475 这个漏洞也过去蛮久的了,于是准备把这篇 CVE-2022-42475 漏洞分析分享出来。注:本文不含完整的漏洞利用脚本。
下图为 CVE-2023-27997 的利用录屏, 与本文要讲的 CVE-2022-42475 无关
I learned a lot from @cfreal_ , and it's great to write exploits together with @leommxj.#CVE-2023-27997 pic.twitter.com/nEFndgvoVD
— swing (@bestswngs) June 17, 2023
2022 年 12 月 12 日,Fortinet 官方发布了影响 FortiGate SSLVPN 的 RCE 漏洞 CVE-2022-42475 相关信息。本文对此漏洞的成因进行分析。
测试版本为 7.2.2, 环境的安装和部署可以参考这篇文章: https://blog.csdn.net/meigang2012/article/details/87903878。 另外感谢下 @explorer 网管大哥在部署环境上的帮助。
导入虚拟机后需要配置下网络, 参考 https://docs.fortinet.com/document/fortigate-private-cloud/7.2.0/vmware-esxi-administration-guide/615472/configuring-port-1
有个重点 dns 要设置下:
config system dns
set primary <Primary DNS server>
set secondary <Secondary DNS server>
end
# The default DNS servers are 208.91.112.53 and 208.91.112.52.
需要配置一个 sslvpn ,然后能访问即可。
挂载虚拟机 vmdk 硬盘后, 可以看到有个 rootfs.gz 文件。
[email protected]:/home/user/Desktop/fuck-fortigate/rootfs# ls
bin bin.tar.xz boot data data2 dev etc fortidev init lib lib64 migadmin.tar.xz node-scripts.tar.xz proc sbin sys tmp usr usr.tar.xz usr.tar.xz.chk var
可以看到有如下内容, 我们需要进一步解压 bin.tar.xz
文件夹,使用 sbin 目录自带的命令解压
chroot . sbin/ftar -cf bin.tar bin
chroot . sbin/xz --check=sha256 -e bin.tar
然后需要在 bin 目录中放入后门,第一个是生成一个反弹shell替换 smartctl 文件, 以及我这里放入一个 busybox ,做一个软链接 ln -sn /bin/busybox bin/sh
设备中默认没有 bash (sh)文件 (或者说他的 sh 功能比较鸡肋), 然后重新打包。
# 重新打包 bin 文件夹
chroot . sbin/ftar -cf bin.tar bin
chroot . sbin/xz --check=sha256 -e bin.tar
# 重新打包 rootfs
find . | cpio -H newc -o > ../rootfs.raw
cat ./rootfs.raw | gzip > rootfs.gz
重打包完成后, 我们需要过几个校验,才能正常启动系统。
vmlinux :
解下来是 bin/init:
这里会校验 fgtsum , 失败直接给你重启最后是 rootfs 检查
由于,我是采用 vmware + gdb 的调试方式, 即 使用VMware和GDB进行Linux内核调试 (bestwing.me), 因此我直接写了一个 gdb python 脚本动态修改返回值即可:
这里皮一句,依稀记得这段代码是 chatGPT 帮我生成的。
import gdb
class SetRaxBreakpoint(gdb.Breakpoint):
def __init__(self, bp_expr, rax_value, temporary=False):
gdb.Breakpoint.__init__(self, bp_expr, gdb.BP_BREAKPOINT, False, temporary )
# super(SetRaxBreakpoint, self).__init__(spec, temporary)
self.rax_value = rax_value
self.silent = True
def stop(self):
gdb.execute('set $rax = {}'.format(self.rax_value))
gdb.execute('set architecture i386:x86-64')
gdb.execute('set pagination off')
r1 = SetRaxBreakpoint('*0xffffffff807ac11c', 0)
r2 = SetRaxBreakpoint('*0x4518C9', 1) #
r3 = SetRaxBreakpoint('*0x277fccc', 1)
当系统成功执行后,使用 diagnose hardware smartctl
即可运行我们的后门文件。
在处理 用户 post 数据的时候,
会根据 http header 中的 content-lenght
字段分配 buffer , 然而在分配之前, 即在调用 pool_alloc 函数之前
pool_alloc 有两个参数, 第二个为即将要分配的buffer 大小
rax 为用户请求结构体指针,偏移位置 0x18 存放了 CL 值。先将 CL 放在 eax 寄存器中,使用 lea 指令将其加一后放在 esi 寄存器,再用 movsxd 扩展为 64 bit 值。
在调用 pool_alloc 函数时使用 32 位数值 + 1 拓展成 64 位的方法,这里存在整数溢出。那么我们可以构造特殊的 CL 值,比如 0x1b00000000,经过运算拓展之后会变成 0x1 。会分配一个小的内存空间导致溢出
上面这个是断点是初始化 buffer , 可以看到大小是 1, 之后在 memcpy 处就 会溢出。
这里首先明确一下,我不会公开完整的利用,这里这提一点利用上的思路。利用整体思路参考 Orange 2017 年的文章, 大致思路就是进行进行竞争, 一边在堆上布局 SSL 结构体,一边触发漏洞,然后溢出覆盖 SSL 结构体。
之后就可以控制 PC , 当我们控制 PC 后我们需要确定 padding , 这个步骤比较繁琐, 我拿 PoC 改了一个循环 fuzz 的脚本。
def do_exploit(padding):
...
payload = p64(ret) * padding + 'A' * 0x1000
...
for i in range(123, 384):
padding = int(i )
if do_exploit(padding):
continue
else:
print('timeout ...')
break
然后对 ret 这个gadget 下一个断点, 当触发断点的时候, 脚本会因为timeout 触发异常,然后这附近大概就是咱们的padding。
然后这个时候只需要找栈迁移的gadget 即可。这里我找了的 push rdx ; add bl, byte ptr [rbx + 0x41] ; pop rsp ; pop rbp ; ret
, 这个gadget , 正好可以将栈迁移到 rdi 寄存器所指向的内存地址上。然后将剩下 ret 指令的替换成 pop rax ; ret
这样的gadget, 这样就能一直迁移到可控制的 AAAAAAA
的地方进行 rop 链了。
再阅读这一部分的内容,我突然反应过来其实不需要替换指令, 当前的 ret 指令就足够迁移到可控的
AAAA
的位置进行 ROP 了
由于 fortigate 的这个程序很大,正如 CVE-2023-27997 的作者所说的,
该程序很大,想找到适合的 gadget 来组成 ropchain 仅仅需要花费一点时间就行了。
[2]Configuring port 1 | FortiGate Private Cloud 7.2.0 (fortinet.com)
[3]attacking-ssl-vpn-part-2-breaking-the-Fortigate-ssl-vpn
[4]使用VMware和GDB进行Linux内核调试 (bestwing.me)
[5]CVE-2022-42475 | CataLpa's Site (wzt.ac.cn)
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/2082/