Someone found the master key to the front gate of the castle and has been letting themselves in while you slept
CVE-2026-104935 is an authentication bypass by spoofing (CWE-290) in Ivanti Connect Secure — the SSL-VPN appliance that sits at the internet edge of thousands of enterprise networks. The flaw is embarrassingly simple: the welcome.cgi endpoint is publicly reachable without credentials, but the appliance honors an X-DSCGI-PATH HTTP header to decide which CGI handler actually processes the request. An attacker sends a request to the unauthenticated welcome.cgi with an X-DSCGI-PATH pointing at an *authenticated* handler like wu.cgi — and the appliance obeys, executing the authenticated handler without checking credentials. From there, a race condition in the file-upload handler lets the attacker drop and execute a Perl payload before validation can delete it, achieving arbitrary command execution. Chained with a separate, still-unpatched privilege-escalation bug in the dsinvoked service, the attacker reaches root. Affected versions include ICS 25.1.1.1 and prior, 25.1.2.2 and prior, and 22.8R2.4 and prior. The legacy 22.7R2.x branch is *not* affected. Fixed versions are 25.1.3.0 / 25.1.3.1, 25.1.1.2, and 22.8R2.5.
Ivanti scored this HIGH 8.6 — and that is a dangerous understatement. The CVSS vector (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L) rates Integrity and Availability impacts as Low, which ignores what actually happens after exploitation: the attacker chains to root, dumps every LDAP-stored domain credential from the appliance (including LAPS local admin passwords and BitLocker recovery keys), runs Kerberoast attacks, conducts password spraying against Active Directory, and captures plaintext credentials from LDAP and HTTP Basic Auth traffic passing through the appliance. Volexity documented threat actor UTA0576 doing exactly this since early October 2026 — a full week before the patch dropped. CISA added this to KEV on day zero of disclosure with a remediation deadline of October 16, 2026 (today). This is a CRITICAL vulnerability on a perimeter device that was exploited before anyone could patch it.
5 steps from start to impact.
Target Discovery
/dana-na/auth/url_default/welcome.cgi is trivially fingerprinted. Previdian observed 57 unique attacker IPs scanning across 5 countries within 48 hours of disclosure.- Target must be running ICS version in affected range (pre-25.1.3.0)
- ICS must be reachable from the internet (true by design — it is a VPN gateway)
- None — ICS appliances are internet-facing by architectural necessity
http.html:"welcome.cgi" or http.title:"Ivanti Connect Secure". Nuclei template available since Oct 14, 2026 at projectdiscovery/nuclei-templates.Authentication Bypass via X-DSCGI-PATH Header Spoofing
/dana-na/auth/url_default/welcome.cgi with a crafted X-DSCGI-PATH header pointing to an authenticated CGI handler such as /home/webserver/htdocs/dana/fb/smb/wu.cgi. The appliance validates the *URL path* for access control but routes the request based on the *header value* — a classic TOCTOU design flaw. This reaches the authenticated wu.cgi file-upload handler without any credentials. Weaponized tools: ApexBreach PoC (python3 exploit.py --url https://target --command "whoami"), 0x45dd detection tool on Sploitus.- HTTPS connectivity to the ICS appliance on port 443
- None — single HTTP request, no credentials, no user interaction, low complexity
X-DSCGI-PATH header presence in requests to welcome.cgi. Alert on any request where the URL path and the routed CGI handler differ. WAF rules blocking X-DSCGI-PATH header in inbound requests would prevent this step.File Upload Race Condition → Web-User RCE
.zip extension to bypass file-type validation in the wu.cgi handler. The handler writes the file to /home/runtime/tmp/<MD5(filename)> *before* validation occurs. Validation then detects the invalid content and deletes the file — but there is a narrow race window between write and deletion. The attacker sends parallel requests to hit that window and execute the uploaded Perl payload before it is deleted. The Perl script spawns Bash, which decodes a Base64/zlib-compressed Python payload for memory-only execution, leaving minimal disk forensic artifacts.- Successful authentication bypass from step 2
- Race condition window exists (inherent to affected versions)
- Race condition requires parallel requests, but public PoCs automate this reliably
/home/runtime/tmp/. Unusual .zip file creation followed by rapid deletion in ICS temp directories. Process monitoring for unexpected Perl → Bash → Python execution chains.Privilege Escalation to Root via dsinvoked
dsinvoked privileged service by performing in-memory patching of /home/lib/libdsplibs.so to reconfigure the DSCache maintenance task. The modified configuration replaces the legitimate maintenance script path with a Perl interpreter executing attacker-controlled code. When the maintenance task fires, the attacker's payload runs as root. Post-execution, the attacker restores the original DSCache configuration and library patches — a sophisticated anti-forensics technique. Note: This privilege escalation uses a *separate, still-unpatched vulnerability* — there is no vendor fix for this component as of October 16, 2026.- Web-user code execution from step 3
- dsinvoked service running (default on all ICS)
- The priv-esc vulnerability is unpatched but not publicly documented in detail — however, UTA0576 has demonstrated it works
libdsplibs.so in memory. Alert on Perl processes spawned by dsinvoked. Check DSCache configuration integrity. Volexity notes that Ivanti's Integrity Checker Tool (ICT) may not detect this compromise.Credential Harvest & Domain Pivot
ml.py, rq5.py) to extract domain passwords, LAPS local admin passwords, and BitLocker recovery keys from Active Directory; a Kerberoast tool (mk.py) to request service tickets for offline cracking; a password spraying tool (sp_run.py) against AD; and a packet capture parser (h.py) extracting plaintext LDAP credentials, HTTP Basic Auth, and NTLM hashes from traffic transiting the appliance. All results were staged in /tmp/ as JSON files and exfiltrated over the existing HTTPS exploit channel — no C2 implant, no web shell, no separate backdoor.- Root access from step 4
- ICS appliance configured with LDAP/AD integration (standard enterprise deployment)
- None for credential extraction — the VPN appliance *must* have AD connectivity to function
- No separate C2 channel needed — re-exploiting the chain for each command is noisy but effective
ms-Mcs-AdmPwd, ms-FVE-RecoveryPassword). Alert on bulk AD object enumeration from the VPN gateway IP. Network segmentation between the ICS management plane and AD can limit blast radius.X-DSCGI-PATH header. This breaks step 2 of the attack chain — the authentication bypass — without requiring an appliance-level patch. Deploy within hours as a stopgap if patching requires a maintenance window. Cloudflare, ModSecurity, and AWS WAF rules are in development per Previdian.ms-Mcs-AdmPwd for LAPS and ms-FVE-RecoveryPassword for BitLocker). This does not prevent initial compromise but limits the blast radius of post-exploitation credential theft from domain-scale to appliance-local. Deploy within 3 days per the noisgate mitigation SLA./tmp/ (ml.py, rq5.py, mk.py, sp_run.py, h.py), hidden directories like /data/.icscap/, renamed tcpdump binaries, gaps in /home/runtime/dlogs/debuglog and /var/log/audit/audit.log. Check for anomalous LDAP queries from the ICS appliance IP. If compromise is confirmed, rotate all credentials accessible via LDAP from the appliance — including LAPS passwords and BitLocker recovery keys.- MFA on VPN user sessions — The bypass operates at the CGI routing layer, before the authentication/MFA stack is ever invoked. MFA protects legitimate user logins; it does not protect against
X-DSCGI-PATHheader spoofing to reach authenticated handlers without logging in at all. - Ivanti Integrity Checker Tool (ICT) as sole detection — Volexity explicitly notes that ICT may not detect this compromise. UTA0576's anti-forensic techniques (in-memory library patching, log tampering, DSCache restoration) were specifically designed to evade ICT. Use ICT as one signal among many, not as a clean bill of health.
- IDS/IPS signature-only detection — Exploitation occurs over HTTPS (port 443). Unless you are performing TLS inspection on traffic to your VPN appliance (uncommon — it's the TLS termination point), network IDS cannot see the
X-DSCGI-PATHheader. Detection must happen on-appliance or via WAF in the TLS path. - IP-based blocking of known attacker IPs — Previdian observed 57 unique IPs in 48 hours across 5 countries. Attacker infrastructure is disposable (Evoxt VPS). Blocking known IOC IPs is useful for retroactive log hunting but provides negligible forward-looking protection.
The supporting signals.
| In-the-Wild Exploitation | Confirmed and active. Threat actor UTA0576 (assessed criminal, not espionage) exploited this as a zero-day from October 3, 2026 — 10 days before the patch. Volexity documented the full attack chain. Previdian observed 118 exploitation attempts from 57 unique IPs across Germany, India, Japan, Singapore, and the US within 48 hours of disclosure. |
|---|---|
| CISA KEV Status | Listed October 13, 2026 — same day as vendor disclosure. Remediation deadline set to October 16, 2026 (today). CISA alert. |
| EPSS Score | 0.25982 (26%) — this places CVE-2026-104935 in approximately the top 1–2% of all scored CVEs by exploitation probability. Extremely high signal. |
| Proof-of-Concept Availability | Multiple public PoCs. ApexBreach/CVE-2026-104935-Proof-of-concept (Python, full RCE). 0x45dd detection PoC on Sploitus (non-destructive bypass check). Nuclei template published October 14, 2026. |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L — Network/Low-complexity/No-auth/No-interaction. Vendor rated I and A as Low, which underrepresents the chained impact to root + credential theft. |
| Affected Versions | ICS 25.1.1.1 and prior, 25.1.2.2 and prior, 22.8R2.4 and prior (Build 19097 was 'fully patched' at time of exploitation). Legacy branch 22.7R2.14 and earlier is NOT affected. |
| Fixed Versions | 25.1.3.0 (partial fix shipped July 2026), 25.1.3.1, 25.1.1.2, 22.8R2.5. Note: the chained privilege-escalation vulnerability remains unpatched — fix in development. |
| Internet Exposure | ~5,500 exposed appliances (Shadowserver, Oct 2026). ~27,000 instances (Censys). ICS appliances are internet-facing by design — they *are* the VPN gateway. |
| Researcher / Discovery | Discovered in-the-wild by Volexity during incident response initiated October 5, 2026. Attack attributed to UTA0576. Volexity assessed with medium confidence that UTA0576 used LLM-assisted exploit development. watchTowr Labs published independent analysis. |
| Chained Vulnerabilities | UTA0576 chained CVE-2026-104935 (auth bypass + RCE) with a separate, unpatched privilege-escalation vulnerability in the dsinvoked service to achieve root. The priv-esc CVE has not been publicly assigned. Full chain: unauth → web-user RCE → root → credential theft → domain pivot. |
Sources.
- Volexity — Chained Exploitation of Ivanti Connect Secure Zero-Days (UTA0576)
- CISA — Adds Two Known Exploited Vulnerabilities to Catalog (Oct 13, 2026)
- Rapid7 — Ivanti Connect Secure Zero-Day Exploited in the Wild
- watchTowr Labs — Ivanti Connect Secure Authentication Bypass Analysis
- ApexBreach — CVE-2026-104935 Proof-of-Concept (GitHub)
- Previdian — CVE-2026-104935 Exploitation Observed (Sensor Data)
- GBHackers — Ivanti Connect Secure CVE-2026-104935 Flaw Enables Unauthenticated RCE
- Nuclei Template — CVE-2026-104935 Detection
Why this verdict
- Zero friction in the attack chain: The vulnerability requires no authentication, no user interaction, has low attack complexity, and is reachable over the network. The public PoC is a one-liner:
python3 exploit.py --url https://target --command whoami. There is no meaningful barrier between an attacker on the internet and code execution on the appliance. - Active zero-day exploitation with credential theft: UTA0576 exploited this for 10 days before any patch existed. The post-exploitation workflow — extracting LAPS passwords, BitLocker keys, Kerberoasting, password spraying — is a direct path to domain compromise. This is not theoretical; Volexity documented it happening.
- Role multiplier: Ivanti Connect Secure is a network edge appliance from the high-value role catalog. By definition, 100% of ICS installs are internet-facing perimeter devices — that is the product's entire purpose. The blast radius is not host-level; it is domain-scale and fleet-scale. A compromised VPN gateway gives the attacker: (1) root on the appliance, (2) every credential transiting the VPN in real-time via packet capture, (3) LDAP access to AD including LAPS and BitLocker keys, (4) Kerberoast and password-spray capability against the domain. In enterprise deployments, ICS is often the *sole* remote-access path, making it identity-scale and network-scale simultaneously. Because this is a canonical high-value-role component (≥10% — indeed 100% — of installs occupy the high-value perimeter role) and the chain ends in domain takeover, the verdict floor is CRITICAL.
- Public PoCs + Nuclei template lower the bar to commodity exploitation: Within 24 hours of disclosure, a working Python exploit and a Nuclei detection template were public. The EPSS of 0.26 (top ~1–2% of all CVEs) corroborates that mass exploitation is imminent or already underway. Previdian data shows 57 unique attacker IPs within 48 hours.
- Vendor CVSS underrates impact: The vendor vector scores Integrity and Availability as Low (I:L/A:L), which reflects only the initial authentication bypass in isolation. The real-world chain — to root, to credential theft, to domain compromise — demands C:H/I:H/A:H with Scope Changed. The 8.6 HIGH rating gives defenders a false sense of scheduling flexibility on what is an emergency.
Why not higher?
A 9.6 is already near the ceiling. The only reason this is not a 10.0 is that the privilege-escalation chain to root relies on a second vulnerability (the unpatched dsinvoked bug), meaning the auth bypass alone delivers web-user RCE rather than immediate root. Additionally, the legacy 22.7R2.x branch is unaffected, which narrows the vulnerable population slightly. If/when the priv-esc component gets its own CVE and a public PoC, the combined chain approaches 10.0.
Why not lower?
Every downward pressure is absent. There are no prerequisites beyond internet reachability — which is guaranteed for a VPN appliance. There is no user interaction. Complexity is low. Public exploit code exists. Active exploitation is confirmed. The product is canonically a perimeter device, making 'requires network access' a vacuous condition rather than meaningful friction. The blast radius extends from the appliance to the domain. Dropping this below CRITICAL would require evidence that the exploit chain fails in practice — and Volexity's incident report proves the opposite.
Crowdsourced verification payload.
Run from any auditor workstation with Python 3.6+ and network access to the target ICS appliance on port 443. No special privileges required. Invoke as: python3 check_cve_2026_104935.py https://vpn.company.com. The script performs a non-destructive version check only — it does NOT attempt exploitation.
#!/usr/bin/env python3
"""CVE-2026-104935 Vulnerability Checker for Ivanti Connect Secure.
Non-destructive version-based check. Does NOT attempt exploitation.
Exit codes: 1=VULNERABLE, 0=PATCHED, 2=UNKNOWN
"""
import sys
import ssl
import re
import urllib.request
import urllib.error
def get_ics_version(target_url):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
urls = [
f"{target_url}/dana-na/auth/url_default/welcome.cgi",
f"{target_url}/dana-na/auth/url_0/welcome.cgi",
f"{target_url}/",
]
for url in urls:
try:
req = urllib.request.Request(url, headers={"User-Agent": "noisgate-checker/1.0"})
resp = urllib.request.urlopen(req, timeout=15, context=ctx)
body = resp.read().decode("utf-8", errors="replace")
hdrs = dict(resp.getheaders())
for pat in [
r'version["\s:=]+(\d+\.\d+(?:\.\d+)*(?:R\d+(?:\.\d+)*)?)',
r'(25\.\d+\.\d+\.\d+)',
r'(22\.\d+R\d+\.\d+)',
]:
m = re.search(pat, body, re.IGNORECASE)
if m:
return m.group(1)
srv = hdrs.get("Server", "")
if any(k in srv for k in ("ICS", "Ivanti", "Pulse")):
m = re.search(r'(\d+\.\d+(?:\.\d+)*(?:R\d+(?:\.\d+)*)?)', srv)
if m:
return m.group(1)
if "welcome.cgi" in url and resp.status == 200:
return "ICS_DETECTED_NO_VERSION"
except (urllib.error.URLError, urllib.error.HTTPError, OSError):
continue
return None
def is_vulnerable(v):
if v == "ICS_DETECTED_NO_VERSION":
return None
# 22.xRy.z
m = re.match(r'22\.(\d+)R(\d+)\.(\d+)', v)
if m:
minor, rel, patch = int(m.group(1)), int(m.group(2)), int(m.group(3))
if minor <= 7:
return False # 22.7 and below unaffected
if minor == 8 and rel == 2:
return patch <= 4 # 22.8R2.4 and prior vuln; .5 fixed
return None
# 25.x.y.z
m = re.match(r'25\.(\d+)\.(\d+)\.(\d+)', v)
if m:
mi, mc, pa = int(m.group(1)), int(m.group(2)), int(m.group(3))
if mi > 1:
return False
if mi == 1:
if mc >= 3:
return False # 25.1.3.0+ fixed
if mc == 1:
return pa <= 1 # 25.1.1.1 vuln; .2 fixed
if mc == 2:
return pa <= 2 # 25.1.2.2 vuln
return True
return None
def main():
if len(sys.argv) != 2:
print(f"Usage: {sys.argv[0]} <https://ics-appliance-url>")
print(f"Example: {sys.argv[0]} https://vpn.company.com")
sys.exit(2)
target = sys.argv[1].rstrip("/")
if not target.startswith("http"):
target = f"https://{target}"
print(f"[*] CVE-2026-104935 Checker - Ivanti Connect Secure")
print(f"[*] Target: {target}")
print()
ver = get_ics_version(target)
if ver is None:
print("[?] Could not detect Ivanti Connect Secure on target.")
print("UNKNOWN")
sys.exit(2)
if ver == "ICS_DETECTED_NO_VERSION":
print("[!] ICS detected but version not extractable.")
print("[!] Check manually against fixed: 25.1.3.0, 25.1.1.2, 22.8R2.5")
print("UNKNOWN")
sys.exit(2)
print(f"[+] Detected version: {ver}")
result = is_vulnerable(ver)
if result is True:
print(f"[!] {ver} is VULNERABLE to CVE-2026-104935")
print("[!] Upgrade immediately to 25.1.3.0+ / 25.1.1.2+ / 22.8R2.5+")
print("VULNERABLE")
sys.exit(1)
elif result is False:
print(f"[+] {ver} is PATCHED for CVE-2026-104935")
print("PATCHED")
sys.exit(0)
else:
print(f"[?] {ver} not matched to known ranges.")
print("[?] Fixed: 25.1.3.0, 25.1.1.2, 22.8R2.5")
print("UNKNOWN")
sys.exit(2)
if __name__ == "__main__":
main()