They Got Root on a Fully Patched Ivanti VPN and the Integrity Checker Said Everything Was Fine
Volexity caught an attacker chaining zero-days into unauthenticated root on an Ivanti Connect Secure appliance running the latest build. The payload never touched disk, commands rode in over the same HTTPS requests as the exploit, and Ivanti's Integrity Checker reported no modifications.
The appliance was fully patched. 22.8R2.4, Build 19097. Latest everything.
They got root on it anyway. No credentials. From the internet.
And then — this is the part that should bother you more than the exploit — Ivanti's own Integrity Checker, the tool whose entire job is telling you whether the appliance has been tampered with, reported that nothing had been modified. Volexity's write-up puts it flatly: the Integrity Checker "does not detect any modifications."
I've spent years telling people a VPN appliance is just a Linux box you're not allowed to log into. This is what I meant.
Read this bit first
This story has moved since Volexity published, and if you patched in mid-October you are probably not done.
CISA added two more exploited CVEs on October 30. And CISA's revised alert lists 22.8R2.5 and prior as affected — which matters, because 22.8R2.5 is the release that fixed the original flaw. Patching in October got you off the first bug and left you on the other two.
| CVE | What it is | Added to KEV | Federal deadline |
|---|---|---|---|
| CVE-2026-104935 | Auth bypass + RCE | 13 Oct 2026 | — |
| CVE-2026-111357 | Privilege escalation | 30 Oct 2026 | 2 Nov, with forensic triage required |
| CVE-2026-118527 | Predictable identifiers → authenticated file upload | 30 Oct 2026 | 13 Nov |
Affected per CISA's revised alert: 25.1.1.2 and prior, 25.1.2.2 and prior, 22.8R2.5 and prior.
Upgrade target: 22.8R2.6 or 25.1.1.3.
CISA didn't just say patch, either. The November 2 directive required agencies to do forensic triage and investigate for possible compromise. That's a meaningfully different instruction and it tells you how they're reading this.
Now the technical part.
The auth bypass is beautiful, and I mean that as an insult
Of the bugs in this chain, the first one is the one I'd put on a slide.
The attacker requests /dana-na/auth/url_default/welcome.cgi. That's a public endpoint. It's the login page. It's supposed to be reachable without authentication.
But they also send a header — X-DSCGI-PATH — naming a protected CGI.
And here's the bug, in Volexity's description: the URL is checked for access, while the header determines where the request actually goes.
Read that twice. The thing being access-checked and the thing being executed are two different values.
That's CVE-2026-104935, and it's the same species as a bug I wrote up last week in Gitea's twenty-one-CVE release — authorization evaluated against something other than the thing it authorizes. Different vendor. Different language. Different decade of codebase. Same mistake.
I don't think that's a coincidence either. It's what happens when the access-control layer and the dispatch layer get written at different times by different people, and nobody owns the question of whether they're looking at the same thing.
Then: write first, validate later
The RCE lives in wu.cgi's upload handling. It's a race condition sitting on top of an ordering mistake. Volexity:
Only after writing the file to disk does wu.cgi check whether it is a valid ZIP.
So the file lands. Then it gets validated. Then it gets deleted a moment later, because it wasn't a valid ZIP.
That moment is the window. Fire parallel requests and you can get the file executed between the write and the delete.
I have written this bug. Not this exact one, but this shape — accept the input, put it somewhere real, then decide whether you should have. It's so much easier to write than the correct version, because validating a ZIP in memory before touching disk is a lot more annoying to write.
And you tell yourself the delete makes it safe. The delete does not make it safe. "Safe" quietly assumed nothing else was running.
Then: root
The privilege escalation is the most interesting of the three technically. The exploit patches the in-memory copy of libdsplibs.so inside the cgi-server process, redirects a maintenance task in DSCache, and gets dsinvoked — which trusts DSCache configuration data — to run the attacker's script as root.
That's CVE-2026-111357. CISA describes it as missing authentication for a critical function, letting a local authenticated attacker escalate.
Ivanti only patched it on October 29, sixteen days after Volexity published. For that entire window the auth bypass and RCE were fixed and the thing that turned them into root was not.
And CVE-2026-118527, the newest one, is a generation-of-predictable-identifiers flaw that makes remote authenticated file upload possible. Predictable identifiers plus a file upload path is its own small chain, and it was exploited as a zero-day in the same campaign.
Why the Integrity Checker missed it
This is the bit worth internalising, because it generalises to every appliance you run.
The payload runs Perl, which runs Bash, which runs Python. The Python is base64-encoded, zlib-compressed, and goes through exec(). It exists only in memory. Nothing durable is written.
The library patch is in memory, not on disk. The DSCache change gets reverted during cleanup. The in-memory library is restored.
And commands didn't travel over a separate channel. Volexity: "UTA0576 also passed commands and results through the exploit chain's HTTPS requests rather than a separate command-and-control (C2) channel."
No web shell. No backdoor. No C2 beacon to catch on egress. Every single command re-runs the whole three-stage chain and tidies up after itself.
So an integrity checker comparing files on disk against known-good hashes has nothing to find. It isn't broken. It's answering a question that doesn't apply to this attack.
That's the lesson I'd take if I ran one of these. Your appliance's self-check tells you about disk. Disk is not where this lives. If your detection story for a black-box VPN is "we run the vendor's integrity checker," you've got a hole shaped exactly like this intrusion. Secure.com's write-up says the same thing independently: a clean Integrity Checker result does not rule out compromise.
They went after the logs directly too — selectively removing entries from /home/runtime/dlogs/debuglog and /var/log/audit/audit.log. Not wiping the files. That's loud. Removing lines.
What they did once they were in
This wasn't a smash and grab. The post-exploitation reads like somebody who's done it before:
- Pulled stored credentials off the appliance, including LDAP domain credentials
- Parsed packet captures for plaintext LDAP creds, HTTP Basic auth and NTLM hashes
- Queried Active Directory and exported the results
- Hunted specifically for LAPS passwords and BitLocker recovery keys with a compromised service account
- Deployed a custom Kerberoasting and credential-testing tool
- Password-sprayed AD with a helper script
- Ran a renamed
tcpdumpat/data/.icscap/dsstatmond, sniffing plaintext protocol ports
Look at that list and notice what the appliance actually was here. It wasn't the target. It was a credential vacuum sitting on the boundary, with a stored LDAP bind account and a view of plaintext internal traffic.
That's the thing about edge appliances. They hold domain credentials because they need them to authenticate your users. Root on the appliance is root on a box that already knows how to talk to your directory, and which your directory already trusts.
Indicators
If you run ICS, go look for these now.
The request pattern is the clearest signal: many requests to /dana-na/auth/url_default/welcome.cgi with a mismatched X-DSCGI-PATH header pointing at something like /home/webserver/htdocs/dana/fb/smb/wu.cgi.
Files and paths Volexity observed:
/tmp/ml.py /tmp/rq5.py /tmp/mk.py
/tmp/rr.py /tmp/pu.txt /tmp/sp_out.txt
/tmp/spray_hits.json
/data/.icscap/h.py
/data/.icscap/cap*
/home/runtime/tmp/ics-root-run-inband-*.pl
Process: dsstatmond running out of a hidden .icscap directory. PID 5012 in Volexity's case.
Network: 108.165.147[.]64, an Evoxt VPS. Same box ran an OpenVPN server from September 30 to October 7, 2026.
Volexity's full indicator set is on their GitHub.
Who
Volexity tracks the actor as UTA0576, and they're careful about attribution in a way I appreciate. They assess "with low confidence that UTA0576 has criminal, rather than espionage, motivations."
Low confidence. They say so out loud.
They also assess "with medium confidence that UTA0576 makes use of large language models (LLMs)" for exploit development and post-exploitation.
I don't know what to do with that second one, and I'd rather admit it than build a thesis on it. It's a medium-confidence call from a team that just demonstrated it does careful work, about tooling that leaves no direct evidence. Worth knowing. Not worth a headline.
There's an overlap worth flagging though: Volexity notes a related technique used by UTA0533 against SonicWall SMA appliances. Edge appliances are having a rough couple of years and the techniques are travelling between them.
Timeline
- Oct 3 — exploitation begins
- Oct 5 — Volexity starts investigating a network monitoring customer
- Oct 10 — indicators shared with Volexity TI customers
- Oct 13 — Volexity publishes; Ivanti patches CVE-2026-104935; CISA adds it to KEV
- Oct 29 — Ivanti patches CVE-2026-111357
- Oct 30 — CISA adds CVE-2026-111357 and CVE-2026-118527 to KEV
- Nov 2 — federal deadline for CVE-2026-111357, forensic triage required
- Nov 13 — federal deadline for CVE-2026-118527
Ten days from first exploitation to public disclosure and a patch is good work by the detection vendor. The seventeen days after that, where the chain was only partly fixed, is the gap you were living in.
What this doesn't cover
No exploit code, no request bodies, no reproduction steps. The mechanism is here because you can't hunt for something you don't understand, and the indicators above are what you actually need.
I haven't verified any of this independently — it's Volexity's incident research and I'm reporting it, with CISA's KEV additions and the revised affected-version list layered on top. Confirm the version numbers against Ivanti's own advisory before you plan a maintenance window. I'm giving you 22.8R2.6 and 25.1.1.3 as targets; the 25.1.2.x branch target I could not pin down from a source I trust, so check that one yourself if it's your branch.
I also can't tell you how widely this was used. Volexity describes a specific intrusion at a specific customer. Whether UTA0576 ran it against fifty organisations or one, nobody who's published so far knows.
And if you're compromised, nothing here is an incident response plan. The honest version: an appliance you can't fully inspect, rooted by an in-memory exploit that cleaned up after itself, is not a thing you confidently clean. You rebuild it. You rotate everything it knew.
What I'd actually do
Check your version against the revised affected list, not the one from October. If you're sitting on 22.8R2.5 feeling patched, you're not. Go to 22.8R2.6 or 25.1.1.3.
Then assume the credentials are gone. Everything stored on that appliance, LDAP bind account first, and anything that crossed it in plaintext. The attacker was specifically hunting LAPS passwords and BitLocker recovery keys — so if you were hit, that rotation is much bigger than the appliance's own config, and it reaches into your AD.
Go hunting in /tmp/ and for that header-mismatch pattern in your logs. Assuming your logs still have it. They might not.
Then the uncomfortable structural question: how much does that appliance know, and why? A box on your perimeter holding domain credentials and watching plaintext internal traffic is a design decision, not a law of physics. I've argued for zero-trust access patterns for self-hosters and this is the enterprise version of the same argument. The blast radius here came from how much the appliance was trusted. Not from the bugs.
Because the bugs were ordinary. Check one value, dispatch on another. Write the file, then validate it. Trust a config store you shouldn't.
The thing that'll stay with me is the Integrity Checker reporting clean. Somebody, somewhere, ran that during this intrusion and felt relieved.
$ tail -f /forums
Discussion
Comments live on the community forums — sign in there to reply.