XORtigate:Fortigate VPN 上的预身份验证远程代码执行 (CVE-2023-27997)
2023-6-15 10:41:54 Author: Ots安全(查看原文) 阅读量:53 收藏

在为我们的一位客户进行红队评估期间,我们有机会了解 Fortigate SSL VPN,这是全球最常用的 VPN 解决方案之一。我们在 VPN 面向互联网的接口上发现了一个堆溢出漏洞。此漏洞无需身份验证即可访问,可用于在 Fortigate 实例上远程执行代码。CVE-2023-27997已分配,CVSS 为 9.2(但实际上是 10)。我们相信这个错误已经存在很长时间了(超过 7.x 和 6.x 分支)。有关受影响版本的详细信息,请参阅 FG-IR-23-097 。

我们将在这里描述两个架构上的漏洞和利用过程,以及一些给蓝队的提示。

错误

该错误位于允许用户对 VPN 进行身份验证的 Web 界面上。这个界面在设计上是面向互联网的。如果我们命中路径,我们可以通过 GET 或 POST/remote/hostcheck_validate发送一个名为 的 HTTP 参数。encparameter,现在好像用的不多,好像是Fortigate跨请求转发HTTP参数的老办法了。

该enc参数是一个包含种子、大小(2 字节)和数据的结构。大小和数据都是加密的。

enc 数据包:种子、大小、数据

种子存储为 8 个十六进制字符,用于计算 XOR 密钥流的第一个状态:

salt是由服务器创建的随机值,可以通过向 发出 GET 请求来检索/remote/info。

密钥流的其他状态计算如下:



密钥流示例值

密钥流小号可以与其余的enc有效负载、大小和密文进行异或运算,以对它们进行解密。它作为十六进制字符串发送。

解密方法如下所示(简化代码):

该函数的行为如下:

  1. 计算 MD5(16 字节),这是来自盐和种子的密钥的第一个状态(前 8 个字符in

  2. 分配大小为in_len / 2 + 1,out和十六进制解码输入的缓冲区

  3. given_len通过将有效负载的前两个字节与密钥的前两个字节进行异或运算,计算用户给出的长度

  4. 边界检查:验证给定的长度不大于缓冲区的大小

  5. 就地解密整个字符串:对前 14 个字节进行 XOR,然后计算一个新状态K1个,用它对接下来的 16 个字节进行异或,然后重复。

  6. 在解密数据的末尾放置一个 NULL 字节

  7. 将解密值添加到包含 HTTP 输入参数的哈希图中

但是,当程序检查给定长度不大于发送的有效负载的长度时,它搞砸的不是一次,而是两次首先,我们可以看到实际大小仅与给定大小的低有效字节进行比较。

if (in_len - 5 <= out[4] ^ md5[0]) // <-- This should be: in_len - 5 <= given_len

第二个问题是以十六进制(例如in_len)描述有效负载的长度,而以原始字节(例如)描述其大小。因此,可以是应有的两倍大。所以,即使演员阵容制作得当,并与进行比较,我们仍然会有错误!'41424343'given_len 'ABCD'given_lengiven_lenin_len

这个漏洞让我们不仅可以将解密过程应用于 中的密文out,还可以应用于之后的内存。

这导致了一个有趣的错误:我们不只是覆盖堆中的字节,而是用一些 MD5 对它们进行异或运算!

漏洞利用实践

现在我们有了一个像样的原语,我们需要找到一些东西来覆盖。

选择的目标:SSL

2019 年,Meh Chang 和 Orange Tsai 利用了同一个二进制文件的堆溢出,并选择修改名为SSL的结构。每当客户端连接到 的工作进程时,就会分配这种结构sslvpnd,并在客户端或服务器关闭套接字时销毁。

这对我们来说是一个完美的目标。

首先,因为我们的原语要求我们多次触发错误,而且我们需要攻击在 HTTP 请求中持续存在的堆结构。

其次,因为该结构包含一个回调处理程序,handshake_func. 由于二进制文件不是 PIE,我们可以修改这个函数指针的值,并强制套接字执行 SSL 握手。从那里,一个标准的堆栈枢轴到任何给我们一个 nodejs shell 的地方(没有 sh!)。

要设置堆,我们需要在与我们控制的套接字关联的某些 SSL 结构之前有一个空块。

攻击 64 位

我们的测试环境是在 Intel 64 位上运行的 VM。SSL 结构的大小为 0x1db8 字节,因此它被分配在 0x2000 字节的区域中。

在分配 SSL 结构之前,还会分配一个大小为 0x2000 的缓冲区,以存储客户端发送的原始 HTTP 请求。对于未碎片化的堆,缓冲区位于内存中 SSL 结构的顶部。

这就是我们想要的地方out!幸运的是,如果客户端发送的请求大于 0x2000 字节,程序会重新分配缓冲区,使该区域为空。由于分配器是后进先出法,并且我们控制 的大小out,因此这是一个非常干净的利用:

为 HTTPd 服务创建大量套接字(填补空白):


SSL 结构被创建,与请求缓冲区连续

在最后一个套接字上发送一个巨大的 HTTP 请求,以强制重新分配缓冲区:


重新分配 HTTP 请求缓冲区

使用另一个套接字来利用该漏洞;out在我们需要的地方分配:


out分配在 SSL 结构之上

由于sslvpnd二进制文件很大,因此构建 ROP 链并不难。它留给读者作为练习。!注意:模式很大程度上受此启发。!

攻击 32 位

一段时间后我们发现有针对 32 位架构的 Fortigate 构建,例如 ARM。我们的 redteam 目标运行在这样的处理器上。在 32 位中,SSL 结构的大小基本上是 64 位中的一半(它主要由指针组成),因此它被分配在大小为 0x1000 的区域中。上述堆设置不起作用。

然而,我们非常幸运:除了 0x2000 缓冲区外,程序还分配了一个 0x1000 缓冲区来存储……HTTP 响应。当太大时,它也会被重新分配。不费吹灰之力,我们就找到了一个网页,该网页会回显一些输入,从而导致 32 位漏洞利用:

  • 为 HTTPd 服务创建大量套接字(填补空白)

  • 采用最后一个连接,以及一个带有巨大 POST 参数的请求

  • 参数得到回显,强制重新分配 HTTP 响应缓冲区

  • 使用另一个套接字来利用此漏洞:溢出的缓冲区在我们需要时得到正确分配。

指标

可以通过向以下两个 URL 中的任何一个发出 GET/POST 请求来利用该漏洞:/remote/hostcheck_validate, 和/remote/logincheck。简单的攻击将需要对这些 URL 中的任何一个快速连续地发出多个 HTTP 请求。但是,通过仔细设置堆,可以使漏洞利用速度变慢。

开发后

攻击者有可能只存在于内存中,而不修改磁盘上的任何内容。尽管重新启动可能会删除内存中的有效负载,但有一些方法可以在设备上实现持久性。保护自己的最好方法是PATCH。

示范

该视频展示了我们针对 x64 构建的第一个漏洞:

第二,针对 ARM,效率更高:

结论

在 4 月初报告该漏洞后,Fortinet于2023年 6 月 8日修复了7.2.5、7.0.12、6.4.13和6.2.15版本。他们的回应迅速而亲切。然而,考虑到从 2019 年至今发现的漏洞数量和质量,我们仍然怀疑他们是否对设备进行过适当的安全评估。

原文翻译自:https://blog.lexfo.fr/xortigate-cve-2023-27997.html


文章来源: http://mp.weixin.qq.com/s?__biz=MzAxMjYyMzkwOA==&mid=2247498776&idx=3&sn=67fcc9f2c2f10582f82809215bbf82a3&chksm=9badb553acda3c45d3d4577b80be70053300b9d1568234ec231cf6f2e211819ead4fb04352dc#rd
如有侵权请联系:admin#unsafe.sh