top of page

Hunt Ivanti CVE-2026-104935 when the Integrity Checker says clean

Writer: Inception Security
Inception Security
23 hours ago
7 min read

Picture a Connect Secure box, fully patched at the time, passing a clean Integrity Checker run after its AD service account has been used to search for LAPS passwords and BitLocker recovery keys. Based on what Volexity found on the device it investigated, that outcome is realistic on this chain.


Volexity reported on 13 October 2026 that a previously unknown actor it tracks as UTA0576 chained three bugs in Ivanti Connect Secure to get unauthenticated root. Two are tracked as CVE-2026-104935. The third, a privilege escalation in how dsinvoked trusts DSCache, has no published CVE and no patch yet. Exploitation of one Network Security Monitoring customer began on 3 October on a then fully patched device at 22.8R2.4 Build 19097. Defused told BleepingComputer it saw the first signs from 1 October. CISA added the CVE to KEV the same day as the advisory, with a due date of 16 October 2026.


Why this one is different


The Integrity Checker asks whether files on disk changed, and UTA0576 built the operation so the answer stays no. In Volexity's words: "The Ivanti Integrity Checker may not be able to identify device compromise."


Volexity found no web shell or backdoor because none was needed. Every command is a full re-exploitation, noisy in requests to welcome.cgi but light on disk. Patching closes the bypass and the RCE, yet anything else that lands code as cgi-server can still reach root until the escalation is fixed. Patch now, and assume the AD service account has been read.


How UTA0576 gets root without leaving a file


The first bug is an authentication bypass in CGI routing. The attacker hits the public login page at /dana-na/auth/url_default/welcome.cgi and names a different script in the X-DSCGI-PATH header, such as /home/webserver/htdocs/dana/fb/smb/wu.cgi. The appliance checks the URL to decide whether the request is allowed, then uses the header to pick the script that runs.


The RCE sits in wu.cgi. The attacker uploads a Perl script with a .zip extension, which the handler writes to /home/runtime/tmp/<MD5(filename)> and deletes once it fails the ZIP check. Parallel requests pointing X-DSCGI-PATH at that temp path try to run the script before it disappears. A win runs a Perl, Bash, then in-memory Python chain. Ivanti tracks the bypass and this RCE together as CVE-2026-104935.


That access runs as cgi-server. The third bug lets the exploit patch the in-memory copy of /home/lib/libdsplibs.so, retarget the ScriptUtil/updateOpswatSDK maintenance task at a Perl interpreter instead of /home/bin/updateOpswatSDK.pl, and have the privileged dsinvoked broker run the attacker's script as root. Afterward the DSCache entry and memory patch are restored, and /home/runtime/dlogs/debuglog and /var/log/audit/audit.log are rewritten, though Volexity says not every cleanup step is confirmed to have completed. Volexity says Ivanti is working on a fix for this escalation that will ship in a future release in the coming weeks.


What Volexity, Ivanti and CISA published


Volexity describes one victim organization, with commands and results riding the exploit's HTTPS requests. Post-exploitation went after credentials: LDAP credentials stored on the appliance, limited AD metadata, and LAPS passwords, BitLocker recovery keys and userPassword and unixUserPassword attributes read through the appliance's AD service account. The actor also used a Kerberos tool for credential testing (its Kerberoast feature was present but not seen in use), sprayed passwords, and parsed LDAP packet captures from /data/.icscap/. Volexity assesses with low confidence that the motive is criminal and with medium confidence that the tooling was built with LLM help.


Ivanti says it is aware of "exploitation in the wild affecting a limited number of customers at the time of disclosure." Affected versions are 22.8R2.4 and earlier, 25.1.2.2 and earlier, and 25.1.1.1 and earlier. Fixed versions are 25.1.1.2, 25.1.3.0, 25.1.3.1 and 22.8R2.5, and 22.7R2.14 and earlier is not affected. Versions 22.7 and 22.8 reach end of life on 31 January 2027. Ivanti rates it CVSS 8.6 (High), points customers to Volexity's indicators, and still recommends running the Integrity Checker regularly. The advisory does not cover the escalation.


CISA lists the CVE under CWE-290 (authentication bypass by spoofing). Shadowserver, via BleepingComputer, counts more than 5,500 ICS instances exposed online, which measures exposure, not compromise.


What to change this week


  1. Upgrade every Connect Secure appliance to a fixed version before the 16 October KEV due date. 25.1.3.x runs only on the ISA4500, 6500 and 8500, and Ivanti points 22.8 users to 25.1.1.2.

  2. Rotate the ICS AD and LDAP service account and any other credentials stored on the appliance.

  3. Rotate LAPS passwords and check BitLocker recovery key exposure for machines that account could read (our reading of Volexity's findings), then scope its read rights down.

  4. If you suspect a hit, capture memory before you reboot or rebuild. Volexity found the chain through memory and core dumps.

  5. Run the Volexity YARA rules and IOCs (GitHub link in Sources) against memory captures and disk images with Ivanti or your IR partner.


Four hunts to run (test first)


These hunts use only what Volexity published, looking at traffic in front of the appliance and at your domain controllers behind it. We have not run them on live customer data. Replace every YOUR_* placeholder before you trust a zero.


Hunt in Splunk: front-door volume and the exploitation IP (test first)


(index=YOUR_PROXY_INDEX OR index=YOUR_WAF_INDEX OR index=YOUR_LB_INDEX) earliest="09/30/2026:00:00:00"
  (uri_path="*/dana-na/auth/url_default/welcome.cgi*"
   OR url="*/dana-na/auth/url_default/welcome.cgi*"
   OR "X-DSCGI-PATH")
| eval src_ip=coalesce(src_ip, src, clientip)
| eval ics_dest=coalesce(dest_ip, dest, "unknown")
| eval has_dscgi=if(match(_raw,"(?i)X-DSCGI-PATH"),1,0)
| bin _time span=1m
| stats count values(uri_path) as paths sum(has_dscgi) as dscgi_hits
        by _time, ics_dest, src_ip
| where count>20 OR dscgi_hits>0
| append [
    search earliest="09/30/2026:00:00:00" (index=YOUR_FIREWALL_INDEX OR index=YOUR_NETFLOW_INDEX OR index=YOUR_VPN_INDEX)
      (src_ip="108.165.147.64" OR dest_ip="108.165.147.64"
       OR src="108.165.147.64" OR dest="108.165.147.64")
    | stats min(_time) as first_seen max(_time) as last_seen count
            values(src_ip) as src_ips values(dest_ip) as dest_ips
            values(dest_port) as ports by index
]
| convert ctime(_time) ctime(first_seen) ctime(last_seen)

Point it at proxy, WAF or load-balancer logs that capture request headers, check field names against one known login, and tune the 20 per minute to your baseline. A proxy that strips custom headers hides the signal, so empty may mean blind.


Hunt in Kibana: welcome.cgi requests and the exploitation IP (test first)


The first search covers the front door.


url.path: *dana-na/auth/url_default/welcome.cgi*
or message: "X-DSCGI-PATH"

The second covers the exploitation IP.


source.ip: "108.165.147.64" or destination.ip: "108.165.147.64"
or related.ip: "108.165.147.64"

ECS has no standard header field, so add your custom one if you map headers. Chart results by source IP to see bursts. Look back to 30 September, when Volexity first saw that host running an OpenVPN server.


Hunt in Falcon CQL: logons from the ICS appliance behind the box (test first)


event_platform=Win
| in(field="event_simpleName", values=["UserLogonFailed2", "UserLogon"])
| RemoteAddressIP4="YOUR_ICS_IP"
| groupBy([ComputerName, UserName, event_simpleName], function=[count(), min(@timestamp, as=first_seen), max(@timestamp, as=last_seen)])
| sort(_count, order=desc)

Watch for many failed logons, or successful ones for accounts other than the ICS service account. Falcon cannot see which LDAP attributes were requested, so for LAPS and BitLocker reads use 4662 with a SACL on the domain controllers, Directory Service 1644 with expensive query logging on, or the KQL hunt. Empty often means no DC sensor or a translated address.


Hunt in KQL: front door, IP, and AD signals (test first)


let icsIp = "YOUR_ICS_IP";
let exploitIp = "108.165.147.64";
let lookback = ago(14d);
let sensitiveAttrs = dynamic(["ms-Mcs-AdmPwd","msLAPS-Password","msLAPS-EncryptedPassword",
  "msFVE-RecoveryPassword","msFVE-RecoveryInformation","userPassword","unixUserPassword"]);
union isfuzzy=true
(
  CommonSecurityLog
  | where TimeGenerated >= lookback
  | where (RequestURL contains "/dana-na/auth/url_default/welcome.cgi"
           and (AdditionalExtensions contains "X-DSCGI-PATH"
                or Message contains "X-DSCGI-PATH"))
        or SourceIP == exploitIp or DestinationIP == exploitIp
  | project Timestamp=TimeGenerated, DeviceName, SourceIP, DestinationIP,
           RequestURL, Message, Signal="proxy_or_firewall"
),
(
  DeviceNetworkEvents
  | where Timestamp >= lookback
  | where RemoteIP == exploitIp or LocalIP == exploitIp
  | project Timestamp, DeviceName, LocalIP, RemoteIP, RemotePort,
           ActionType, Signal="device_network"
),
(
  IdentityQueryEvents
  | where Timestamp >= lookback
  | where IPAddress == icsIp
      or AccountUpn has "YOUR_ICS_AD_SERVICE_ACCOUNT"
      or AccountName has "YOUR_ICS_AD_SERVICE_ACCOUNT"
  | where Query has_any (sensitiveAttrs)
        or tostring(AdditionalFields) has_any (sensitiveAttrs)
  | project Timestamp, AccountName, AccountUpn, IPAddress, QueryType, Query, QueryTarget,
           DeviceName, Signal="identity_query"
),
(
  SecurityEvent
  | where TimeGenerated >= lookback
  | where EventID in (4625, 4768, 4769, 4771)
  | extend SrcIp = replace_string(IpAddress, "::ffff:", "")
  | where SrcIp in (icsIp, exploitIp)
  | project Timestamp=TimeGenerated, Computer, EventID, IpAddress,
           TargetUserName, Signal="ad_auth"
)
| sort by Timestamp asc

The attribute names are our reading of the standard LAPS and BitLocker schema, not strings Volexity published. Defender for Identity logs only some LDAP searches, so a zero there is weak evidence. Run it in Sentinel or the unified Defender portal with the Sentinel workspace connected, because standalone Defender XDR quietly returns nothing for CommonSecurityLog and SecurityEvent.


Interpret before you escalate


Finding

Benign read

Escalate when

Burst of requests to welcome.cgi

Normal login load

Parallel requests seconds apart, or any X-DSCGI-PATH

URL path and X-DSCGI-PATH disagree

None we know of from outside the appliance

Any mismatch Volexity described; attackers may find other paths

Traffic to or from 108.165.147[.]64 since 30 September 2026

None we know of

Any hit

ICS AD account reading LAPS, BitLocker or password attributes

None in normal operation

Any such query; rotate and scope the account

4768, 4771 or 4625 from the ICS IP in bursts

Normal VPN auth noise

Spray-shaped failures you cannot explain

/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/dsstatmond or /home/runtime/tmp/ics-root-run-inband-*.pl in an image or memory capture

None

Treat as compromise. rq5.py and rr.py were deleted, so their absence proves nothing

A .icscap directory under /data/ or /home/root/data/

Rare, inspect first

Capture files or a renamed tcpdump inside

DSCache ScriptUtil/updateOpswatSDK pointed at a Perl interpreter

None

Any hit; capture memory before reboot

Clean Integrity Checker result

Disk files look unchanged

Treat as not checked yet; hunt off-box and in memory

Empty results across all four hunts

Nothing matched

Confirm header logging, DC coverage and account naming first


Sources



What This Means for Your Team


On this chain, a green Integrity Checker result does not mean the box is clean. The evidence lives in front of the appliance, behind it on the domain controllers, and in memory. Upgrade this week, rotate the service account and what it could read, and watch for the escalation fix. Ivanti calls exploitation limited and public reporting describes one victim, but treat your estate as unchecked until the hunts return on logging you trust.


Inception Protection


Inception Protection is our managed detection and response service, and it works with the Microsoft licenses you already have. In our experience, VPN and edge compromise often shows up first in directory and sign-in activity behind the appliance, not on the appliance disk. We help you pull those signals into one place and start containment with you. You still make the patch and rotation calls.


Inception Foresight


If you want to see how well your Microsoft environment would surface activity like this, try the free Inception Foresight M365 Assessment: https://www.inceptionsecurity.com/m365assessment. You can also follow Inception Security on LinkedIn and @inceptionsec on X.


bg-map-white.png

INCEPTION SECURITY™

A cybersecurity company with in depth knowledge of the threat landscape and security controls.

NAVIGATION

GET IN TOUCH

© 2025 All Rights Reserved by INCEPTION SECURITY™ .

bottom of page