4

CVE-2022-42475-FortiGate-SSLVPN 堆溢出漏洞分析

 1 year ago
source link: https://paper.seebug.org/2082/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

CVE-2022-42475-FortiGate-SSLVPN 堆溢出漏洞分析

11小时之前2023年06月20日漏洞分析

作者: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 相关信息。本文对此漏洞的成因进行分析。

1. 环境配置

测试版本为 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 ,然后能访问即可。

2. 设备权限获取

挂载虚拟机 vmdk 硬盘后, 可以看到有个 rootfs.gz 文件。

root@Jas-22:/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 :

1687253978000-1ueufk.png-w331s

解下来是 bin/init:

1687253979000-2euwis.png-w331s

这里会校验 fgtsum , 失败直接给你重启最后是 rootfs 检查

1687253980000-3zyacg.png-w331s

由于,我是采用 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 即可运行我们的后门文件。

3. 漏洞分析

Root Cause

在处理 用户 post 数据的时候,

1687248812000-4xvlqn.png-w331s

会根据 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 。会分配一个小的内存空间导致溢出

1687248812000-5wkkez.png-w331s

上面这个是断点是初始化 buffer , 可以看到大小是 1, 之后在 memcpy 处就 会溢出。

1687248812000-6isffd.png-w331s

exploit

这里首先明确一下,我不会公开完整的利用,这里这提一点利用上的思路。利用整体思路参考 Orange 2017 年的文章, 大致思路就是进行进行竞争, 一边在堆上布局 SSL 结构体,一边触发漏洞,然后溢出覆盖 SSL 结构体。

1687253980000-7ztklm.png-w331s

之后就可以控制 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。

1687253981000-8vbiii.png-w331s

然后这个时候只需要找栈迁移的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 的作者所说的,

image-20230618171021882

该程序很大,想找到适合的 gadget 来组成 ropchain 仅仅需要花费一点时间就行了。

4. 参考连接

[1]飞塔老梅子的博客-CSDN博客

[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)

[6]fortios 5.4后门植入)


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/2082/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK