Ivanti Connect Secure exploited chain: X-DSCGI-PATH dispatch split, write-then-delete execution race, and VPN-edge post-exploitation tradecraft¶
Event in one line: On 2026-10-13 Ivanti patched and CISA KEV'd CVE-2026-104935 (authentication bypass by spoofing, CWE-290, unauthenticated RCE, KEV due date 2026-10-16), and Volexity published the full technical chain behind it: a front-end/back-end dispatch split where the URL path decides authorization but an X-DSCGI-PATH header decides which CGI actually runs, chained to a write-then-validate file race in wu.cgi (file written to a client-predictable MD5-derived path, executed via the same header trick before the invalid-ZIP check deletes it), chained to a root privilege escalation through the dsinvoked broker trusting DSCache task definitions — unauthenticated root RCE on a fully patched 22.8R2.4 appliance, with a separate unpatched privesc still open.
Why this is durable operator content: every leg is a reusable hunt pattern on appliances and monolithic web stacks (URL-vs-header dispatch differentials, MD5-of-filename predictable write paths, execute-before-delete races, privileged brokers trusting shared config stores), and the post-exploitation inventory is a field manual for what to collect from a compromised VPN concentrator during an authorized red-team engagement.
Primary sources¶
- Volexity, A Series of Unauthenticated Events: Chained Exploitation of Ivanti Connect Secure Zero-Days (2026-10-13, Charlie Gardner, Sean Koessel, Steven Adair, Volexity Volcano Team)
- CVE Record CVE-2026-104935: "Authentication bypass by spoofing in ICS before version 25.1.3.0 allows a remote unauthenticated attacker to execute arbitrary commands"; unaffected bands 25.1.3.0/25.1.3.1, 22.8R2.5, 22.7R2.14
- CISA KEV entry added 2026-10-13 (catalog 2026.10.13), due 2026-10-16; CVE.org record and Volexity writeup for technical detail
- Historical lineage: CVE-2023-46805 + CVE-2024-21887 (same product family, front-end/back-end parser differential), CVE-2024-21893 SSRF
The chain, leg by leg (as published)¶
- Authentication bypass — dispatch split. Request
GET /dana-na/auth/url_default/welcome.cgi(public, auth-free), and add the headerX-DSCGI-PATH: /home/webserver/htdocs/dana/fb/smb/wu.cgi(a non-public CGI running in a trusted context). The URL is checked against the auth gate; the header selects the destination handler. Result: any protected CGI on the box is reachable through a public URL by naming it in the header. - RCE — write-then-validate race.
wu.cgi's upload handler takes a file (attacker used a Perl script named*.zip), computes MD5 of the client-supplied filename, and writes the raw content to/home/runtime/tmp/<MD5(filename)>before validating that it is a real ZIP. Validation failure deletes the file — but it exists on disk for a window. The attacker fires parallel requests withX-DSCGI-PATH: /home/runtime/tmp/<MD5(filename)>(leg 1's primitive now doubles as an execution router) and wins the race before deletion. Repeat bursts raise hit probability. Payload chain was Perl → Bash → in-memory Base64+zlib Python run viaexec(), so the real payload never touched disk. - Privilege escalation — broker trusts shared config store. The privileged root service
dsinvokedlooks up each maintenance task's program path inDSCache, a shared ICS config store. The in-memory payload patches thecgi-serverprocess's copy of/home/lib/libdsplibs.so(single instruction byte, via/proc/self/mem), uses it to rewrite aDSCachetask entry to point at the Perl interpreter, asksdsinvokedto run the task with a malicious script argument → root. Then it restores the DSCache entry and the patched byte, checks/etc/shadowreadability as the success oracle, and excises its window from/home/runtime/dlogs/debuglogand/var/log/audit/audit.log. The on-disk library is never modified, so Ivanti's Integrity Checker Tool sees nothing. - Operation shape. No web shell, no backdoor, no separate C2: the entire chain is re-executed per command and results return in the HTTPS response ("in-band" channel). First-stage to full root, repeated at will, disk-clean.
Hunt patterns this generalizes (any appliance, not just ICS)¶
- URL-gate vs header-dispatch differential. Fingerprint any stack that accepts
X-*PATH,X-Original-URL,X-Rewrite-URL,X-DSCGI-*, or similar routing headers: send the header on a public URL and diff behavior against the same URL alone. The auth decision and the dispatch decision living in different components (front-end vs back-end/CGI broker) is the exact class behind CVE-2023-46805, CVE-2024-21887, and now this. On the target, header acceptance on public endpoints with a content/response mismatch is boundary-crossing evidence — treat protected-path dispatch as authorized-window testing only. - Predictable write path from attacker input. MD5(filename-you-supplied) is fully attacker-computable pre-upload. Grep/attack-surface rule: any upload handler whose destination path is derived from a hash or transform of a client-controlled name yields a raceable, addressable write primitive.
- Write → validate → delete = race window. The security check happens after the write and before (via deletion) any potential execution. Anything that can touch the file in between — and on these boxes the same header-router can execute it — beats the validator. This is the validate-vs-write axis from the wiki's Sept 19 WP upload page with a built-in execution router stapled on.
- Privileged broker that reads its task table from shared config. If a root service's "which program do I run" answer comes from a store the lower-privilege context can influence (DSCache, cron-like tables, plugin registries, unit files), poisoning one entry converts
cgi-serveraccess into root. Check whether task lookups are validated against an on-disk allowlist or just trusted state. - In-band request/response C2 defeats beacon-based detection. No persistence, no C2 host, per-command re-exploitation: nothing for endpoint or network beacon detection to see. For detections/red-team dashboards, the signal is the request pattern, not any implant.
Authorized validation boundary (perimeter appliance, KEV-active product)¶
This product is under active mass exploitation; the auth bypass itself creates real sessions and logs. Boundaries:
- Confirm scope and ownership first (scope doc, cert SANs,
/dana-na/branding, inventory). Never probe out-of-scope ICS/Neurons/Pulse nodes. - Version evidence without exploit: ICS admin-disclosure paths and version strings per customer permission; fixed bands are 25.1.3.0+, 22.8R2.5+, 22.7R2.14 and prior. A patched-but-exploitable-before-Oct-13 window matters for engagements — ask the customer for patch date vs engagement dates.
- Header-acceptance probe (low-impact): request a public welcome page with and without an
X-DSCGI-PATHheader naming another public path; log any response/timing/length differential proving header-influenced dispatch exists. Do not point the header at protected CGIs or/home/runtime/tmp/paths against production — that crosses the boundary and is lab-or-approved-window only. - Lab validation on an owned/downgradeable ICS instance is the only place to prove the race or protected-CGI reachability; proof should be a marker file or
id-style output inside the lab, never production command execution. - Evidence to keep: scoped host, source IP, timestamps, request/response artifacts with the header battery, version/patch evidence, and (authorized recon only) whether the box runs
dsstatmond-style hidden-directory pcap collectors. Keep credentials, LDAP material, config dumps, and real session data out of every artifact.
VPN-edge post-exploitation inventory (red-team tradecraft from the published activity)¶
Volexity's reconstruction of UTA0576's actions on the concentrator doubles as a checklist of what a compromised edge VPN device is for:
- Harvest the appliance's own secrets first: LDAP/domain service-account credentials stored on-box, auth-method config (
/home/etc/pulse/,/home/etc/,/home/*.cfg), local auth logs mined for valid usernames. - Sniff the plaintext it already sees: the device sits inline with LDAP/HTTP/FTP/telnet traffic; a renamed
tcpdump(dsstatmond) writing rotating pcaps under a dot-prefixed hidden dir (/data/.icscap/cap*), parsed offline for LDAP simple binds, HTTP Basic, and NTLM challenge material. - Live off the directory with the service account: LDAPv3 queries for LAPS passwords, BitLocker recovery keys,
userPassword/unixUserPasswordattributes, logon-script paths; results as zlib+Base64 JSON in/tmp/exfiltrated over the same in-band channel. - Kerberos on the edge: custom Kerberos tooling for service-ticket requests (Kerberoast-capable) and offline credential testing straight from the VPN's network position.
- Spray from inside the perimeter: password-spraying helpers run on the appliance so the traffic looks like the VPN's own.
- Stay clean: selective log-window excision, self-deleting payload files, restored state,
/dev/nulleverything — assume the integrity checker is blind to memory-only patches.
For defensive side of an engagement scope this maps directly to "what would an attacker get from our concentrator" — the same queries are the audit list.
Related wiki pages¶
- Ivanti Sentry unmanaged-appliance command boundary — same vendor class, managed-state precondition framing
- Check Point IKEv1 VPN authentication-bypass validation — perimeter VPN auth-boundary validation discipline
- Ivanti Neurons ITSM deserialization and missing authz — adjacent Ivanti product family