CVE-2022-42475
2022 年 12 月 12 日,Fortinet 官方发布了影响 FortiGate SSLVPN 的 RCE 漏洞 CVE-2022-42475 相关信息。官方公告显示该漏洞已经被发现在野利用,建议所有用户尽快升级。本文对此漏洞的成因进行分析。
环境准备
Fortinet 官方对 Fortigate 等设备的虚拟机版本开放下载,下载链接:https://support.fortinet.com/Download/VMImages.aspx
下载到虚拟机镜像后导入 vmware 安装,第一次启动先配置网络
1 | 使用默认用户 admin:空密码 登录到 CLI |
配置好网络后通过浏览器访问到设备 web 界面,首次登录系统会要求导入 license。这里有两种选择,一是完整 license,二是试用版。我们选择试用版 license,先去官方网站注册一个 FortiCloud 账号,然后在系统上登录,等待重启即可。(也可以参考文章尝试破解 License 授权)
注意!如果选择使用评估版本 License,设备会进入 LENC 模式,在该模式下不能使用高级加密算法,相应的,只能使用 SSLv3 或 TLSv1.0 等过时的加密链接。此时可能会遇到 SSL_VERSION_OR_CIPHER_MISMATCH 等错误。
漏洞位于设备的 SSLVPN 功能中,分析前需要配置 VPN 功能。配置过程可参考官方文档,简单来说,首先在 User & Authentication -> User Definition 功能中创建一些 VPN 账户,添加到同一个 group 中。然后在 VPN -> SSL-VPN Settings 中填写监听网卡和端口等信息。最后按照提示创建一条防火墙规则允许外部请求进入。
这样访问对应接口即可看到 SSLVPN 界面。
代码和权限获取
我们采用挂载磁盘的方法,关闭虚拟机,将较小的磁盘卸载并挂载到另一台 Linux 系统上,开机之后看到系统识别到一些硬盘分区:
在 FORTIOS 分区中的 rootfs.gz 是主要文件系统,将其解压得到一些系统文件,但 bin 等目录下没有任何内容。我们参考网络上的文章发现关键文件在 bin.tar.xz、migadmin.tar.xz 等压缩包内,这些压缩档案使用 Fortinet 自己修改过的工具打包。具体解包方法,在解压目录下执行命令
1 | sudo chroot . /sbin/xz --check=sha256 -d /bin.tar.xz |
解包之后找到 /bin/init,系统中的大部分业务程序都软链接到该二进制文件,是我们主要的分析目标。
按照相同的方法提取出 7.2.2 和 7.2.3 中的 init 文件,准备进行补丁分析。
权限获取可参考网络文章。
漏洞分析
首先进行补丁对比,将不同版本的 init 程序导入 IDA 分析,保存 idb 之后用 bindiff 比较,需要注意一点,直接使用 bindiff GUI 可能会卡在解包 idb 阶段,建议使用 IDA 中的 bindiff 插件比对,程序较大需要分析较长时间。
比较完成后按照相似度和置信度逐个分析代码差异,新版本对 wad 部分进行了很多修改,除此之外比较明显的修改位于内存分配函数中。
举例来说,7.2.2 版本中某内存分配函数
1 | __int64 __fastcall sub_1776C70(__int64 a1, __int64 a2, unsigned int a3) |
在 7.2.3 中对应函数
1 | __int64 __fastcall sub_1776E60(unsigned __int64 a1, __int64 a2, unsigned int a3) |
我们发现新版本的内存分配相关函数中都添加了对 size 的判断,要求其不能大于 0x40000000
考虑到该漏洞是一个堆内存溢出,根据修复方式推测漏洞的根本原因可能是某处发生整数溢出,导致内存分配函数返回了一块较小的内存,而后续拷贝数据时又使用了较大的 size。
而在 HTTP 请求中可能有两种情况会导致以上结果,一是某些功能 handler 函数中对用户提交的参数验证不严格,或者代码在解析请求时对 Content-Length 的解析出现异常。
sslvpn 中在未授权情况下能够访问的功能点不多,漏洞出现在请求解析阶段可能性比较大。sslvpnd 是基于 Apache httpd 修改而来,开发者在其中添加了很多自定义代码,导致复杂度较高,而且程序不包含符号信息,分析起来会消耗很多时间。
我们可以采取更简单的方法,基于补丁分析和推测,漏洞可能发生在解析请求,特别是处理 Content-Length 阶段。那么只需要按照 fuzz HTTP 协议的思路,构造一些带有畸形 Content-Length 的请求,例如 CL 过大、或者等于负数的情况,将这些请求发送到能够未授权访问的接口中,同时检测 web 服务状态,发生崩溃或无法收到响应时记录下对应的请求报文。
编写出测试脚本:
1 | import socket |
运行后当发送 CL 等于 2147483647 时服务器没有响应,手动测试结果也一致。
挂载调试器尝试捕获异常信息
发包之后产生段错误,访问 rdi 时遇到非法地址。通过栈回溯分析其调用信息,最终找到了关键函数 read_post_data
1 | __int64 __fastcall read_post_data(__int64 a1) |
这个函数负责从 POST 请求体中读取输入,其基本逻辑:首先获取到用户提交的 Content-Length 值,传入 pool_alloc 函数中分配内存空间,之后使用 memcpy 将用户数据拷贝到刚刚分配的内存中。
问题就出在 pool_alloc 参数上面,查看汇编指令
1 | mov eax, [rax+18h] |
rax 为用户请求结构体指针,偏移位置 0x18 存放了 CL 值。先将 CL 放在 eax 寄存器中,使用 lea 指令将其加一后放在 esi 寄存器,再用 movsxd 扩展为 64 bit 值。结合调试信息就可以看到程序为何崩溃。
在 fuzz 脚本中传入 CL = 2147483647,换成 hex 为 0x7fffffff,经过上面的运算当传入 pool_alloc 时寄存器情况:
pool_alloc 函数的伪代码:
1 | void *__fastcall sub_164E590(__int64 a1, size_t a2) |
传入数据参与补齐运算,然后判断在 v3 数组中对应位置是否存在内容,不存在则直接调用 memset 返回,而当调用 memset 时参数情况:
length 部分变成一个非常大的数值,这样会导致 memset 访问到非法内存使程序崩溃。
考察漏洞根本原因,在调用 pool_alloc 函数时使用 32 位数值 + 1 拓展成 64 位的方法,这里存在整数溢出。那么我们可以构造特殊的 CL 值,比如 0x1b00000000,经过运算拓展之后会变成 0x1,在 pool_alloc 内部调用 memset 时情况:
缓冲区是位于 heap 的一块较小内存,而 size 已经变成 0x1。
这样 pool_alloc 返回了一块较小的堆内存,假设此时我们在 POST 请求体中构造了超长的数据,那么在后续的 memcpy 阶段就会导致堆内存溢出。
某些情况下能够得到如下 crash
利用分析
对于 FortiGate 堆溢出的利用,DEVCORE 曾介绍过思路:https://devco.re/blog/2019/08/09/attacking-ssl-vpn-part-2-breaking-the-Fortigate-ssl-vpn/
传统堆溢出利用需要结合堆相关的管理逻辑,通过精心控制堆块排布来控制程序执行流。但正如 DEVCORE 文章和我们 fuzz 结果显示,在 FortiGate 上堆溢出会覆盖堆中某些关键结构体中的数据,具体来说是 HTTP 请求的 SSL 结构体指针。在触发漏洞之前先发送很多正常的 HTTP 请求,这样在堆中就会留下很多 SSL 结构,再触发堆溢出去覆盖这些结构体,当程序调用被覆盖的结构体中 handshake_func 指针时,我们就能直接劫持程序控制流。
观察崩溃现场,rdx 寄存器指向可控内存,我们可以在程序中找到 push rdx ; pop rsp
的 gadget,将 stack 迁移到可控内存中,将堆溢出转换成 ROP,直接执行 system(‘cmd’) 即可。
补丁
新版的 read_post_data 调用 pool_alloc 时代码
1 | mov rax, [rax+18h] |
不再使用 32 位寄存器拓展,并且分配内存时会检查 size 大小。
参考文章/拓展阅读
DEVCORE 关于 FortiGate 堆溢出漏洞利用的文章 。
2023 年 1 月 11 日,Fortinet 官方发布了关于积极利用该漏洞的组织,以及他们所使用工具的分析文章 。
2023 年 5 月 17 日,BishopFox 发布了一种更完整的利用此漏洞的文章 。
- Title: CVE-2022-42475
- Author: Catalpa
- Created at : 2022-12-15 00:00:00
- Updated at : 2024-10-17 08:46:49
- Link: https://wzt.ac.cn/2022/12/15/CVE-2022-42475/
- License: This work is licensed under CC BY-NC-SA 4.0.