Eine vollständig gepatchte VPN-Appliance, und trotzdem Root
Das ist der ganze erste Schritt: ein POST an die öffentliche Login-Seite von Ivanti Connect Secure, welcome.cgi, die überhaupt keine Sitzung braucht, mit einem zusätzlichen Header, X-DSCGI-PATH, gesetzt auf den Pfad einer anderen, normalerweise authentifizierten CGI-Datei. Die Authentifizierungsprüfung liest die URL. Der Header entscheidet, wohin die Anfrage tatsächlich geht. [1]
Volexity verfolgt den Akteur dahinter als UTA0576, und als die technische Analyse am 13. Oktober 2026 veröffentlicht wurde, hatte die Gruppe diesen Header-Trick bereits mit einem zweiten Fehler verkettet, um Codeausführung als der unprivilegierte cgi-server-Benutzer zu erreichen, auf einer Appliance mit einem vollständig gepatchten Build, 22.8R2.4. [1] Die beiden Fehler, die dorthin führen, werden gemeinsam als CVE-2026-104935 erfasst: CVSS 8,6, CWE-290 (Authentifizierungsumgehung durch Spoofing), betrifft Ivanti Connect Secure 25.1.0.0 vor 25.1.1.2, 25.1.2.0 vor 25.1.3.0 und jeden 22.8R2-Build bis 22.8R2.4, behoben in 25.1.1.2, 25.1.3.0, 25.1.3.1 und 22.8R2.5 am 13. Oktober 2026. [2] CISA nahm die Lücke am selben Tag in den Known-Exploited-Vulnerabilities-Katalog auf, mit einer Dreitagesfrist für Bundesbehörden, die bereits verstrichen ist. [2][3]
Codeausführung als cgi-server ist nicht das Ende der Geschichte. Volexity rekonstruierte zudem eine dritte Stufe, noch immer ohne CVE-Nummer und ohne Patch, die von dort zu Root führt, ohne Backdoor und ohne Webshell. Ich habe nichts davon selbst ausgeführt; das Folgende beruht auf Volexitys technischer Analyse sowie den CVE- und KEV-Einträgen, gelesen am 18. Oktober 2026. [1][2][3] Der Teil, der Ihre Aufmerksamkeit verdient, ist nicht, dass eine VPN-Appliance kompromittiert wurde. Es ist, dass zwei der drei Fehler genau das sind, was ein Code-Reviewer findet, nicht das, was ein Incident Responder erst danach entdeckt.
POST /dana-na/auth/url_default/welcome.cgi HTTP/1.1
Host: vpn.example.com
X-DSCGI-PATH: /home/webserver/htdocs/dana/fb/smb/wu.cgi
Content-Type: application/x-www-form-urlencoded
(welcome.cgi needs no session at all)
// Host and paths are illustrative, not turnkey.
// The URL says "public login page" -> the auth check reads the URL.
// The header says "run wu.cgi instead" -> routing reads the header.
// Nothing checks that the two agree.Die Login-Seite prüft die URL. Ein Header entscheidet, welches Skript läuft
Jeder Dispatcher wie dieser beantwortet zwei getrennte Fragen für jede Anfrage: Darf dieser Aufrufer hier sein, und welche Datei verarbeitet die Anfrage tatsächlich. ICS beantwortet die erste, indem es den URL-Pfad prüft, also ob dies einer der öffentlichen, unauthentifizierten Endpunkte ist, und die zweite, indem es einen Routing-Header prüft, X-DSCGI-PATH, der das aufzurufende CGI-Skript benennt. [1] Das in zwei Fragen aufzuteilen, ist vernünftiges Engineering. An diesem Design allein ist nichts nachlässig.
Es bricht nur, weil nichts prüft, ob die beiden Antworten übereinstimmen. Der Dispatcher nimmt an, dass eine Anfrage, die das öffentliche Login-Skript trifft, per Definition eine öffentliche Anfrage ist. Der Angreifer kontrolliert einen Header, der still überschreibt, welches Skript tatsächlich zuständig ist. Richten Sie diesen Header auf wu.cgi, einen geschützten Upload-Handler, und eine Anfrage, die wie ein Treffer auf die Login-Seite aussieht, wird tatsächlich zu einem Aufruf in authentifiziertes Gebiet, mit genau dem Vertrauen, das der Dispatcher allem einräumt, das er für bereits durchgelassen hält.
Der Upload-Handler vertraut der Uhr, nicht dem Inhalt
Zu wu.cgi geroutet zu werden, ist Schritt eins. Schritt zwei macht aus diesem Routing Codeausführung, und das ist ein Timing-Fehler, kein Inhaltsfehler. wu.cgi schreibt eine hochgeladene Datei nach /home/runtime/tmp/<MD5 des von Ihnen gewählten Dateinamens>, und erst danach prüft der Handler, ob der Inhalt tatsächlich ein gültiges ZIP-Archiv ist. Ist er es nicht, löscht er die Datei. [1] Benennen Sie ein Perl-Skript mit der Endung .zip, und für ein kurzes Zeitfenster liegt ein Skript, das bereitwillig ausgeführt wird, unter einem Pfad, den Sie bereits kennen, weil Sie den Dateinamen gewählt haben und ein vorhersagbarer Hash ihn in diesen Pfad verwandelt hat.
Volexity beschreibt, wie der Angreifer genau in diesem Fenster parallele Anfragen gegeneinander rennen lässt: genug Anfragen, über den Header-Trick aus Stufe eins geroutet, auf diesen temporären Pfad gerichtet, um das Perl-Skript zu erwischen, bevor die Löschung das Rennen gewinnt. Eine Anfrage, die rechtzeitig landet, reicht. [1]
Die von Volexity gesicherte Payload hörte nicht bei Perl auf. Sie ging in ein Python-Skript über, Base64- und zlib-komprimiert ausgeliefert und per exec() im Speicher ausgeführt, statt als eigene Datei geschrieben zu werden. [1] Das Ziel auf jeder Stufe: möglichst wenig auf der Platte zu hinterlassen, das später jemand findet.
// wu.cgi's order of operations, reconstructed from Volexity's writeup.
// This is the sequence, not a working exploit.
1. Attacker uploads "payload.zip" containing a Perl script, not a real ZIP.
2. wu.cgi writes it to /home/runtime/tmp/<MD5("payload.zip")> immediately.
3. wu.cgi then checks: is this actually a valid ZIP archive?
4. Check fails -> wu.cgi deletes the file.
// The gap between step 2 and step 4 is the whole bug.
// The attacker fires repeated requests (via the stage-one header trick)
// at the path from step 2, racing to execute it before step 4 wins.
// One request landing inside that window is enough.Zwei Stufen behoben. Die dritte nicht, und sie ist die interessante
Ivantis Fix für CVE-2026-104935, ausgeliefert am 13. Oktober 2026, schließt Stufe eins und zwei: 25.1.1.2, 25.1.3.0, 25.1.3.1 und 22.8R2.5 sind die Versionen, auf denen genau diese Kette nicht mehr funktioniert. [2] Wenn Sie auf einer früheren Version in einer dieser drei Linien laufen, ist das kein Punkt für den nächsten Sprint. Die eigenen Ausnutzungsdaten des CVE-Eintrags lauten bereits auf „aktiv", mit „totaler" technischer Auswirkung, und CISAs Dreitagesfrist für Bundesbehörden liegt bereits hinter uns. [2][3]
Bei Stufe drei hören die Patch-Notizen auf. Sobald ein Angreifer Codeausführung als der unprivilegierte cgi-server-Benutzer hat, fand Volexity heraus, dass UTA0576 dsinvoked, ICS' eigenen Root-Wartungsdienst, so umlenkte, dass er vom Angreifer gewählten Code als Root ausführte, indem das Ziel einer bestehenden Aufgabe umgelenkt wird, vollständig im Speicher eines bereits laufenden Prozesses. [1] Ich gehe die konkreten Bibliotheks- oder Konfigurationsdetails, die Volexity genannt hat, nicht durch. Der Mechanismus ist öffentlich, ein Patch dafür nicht, und Detailwissen auf Exploit-Niveau für eine offene Rechteausweitung zu wiederholen, ist nichts, was ich hier tue, unabhängig davon, wer es schon veröffentlicht hat.
Was unabhängig vom Patch-Status zählt: Die Rechteausweitung macht sich selbst wieder rückgängig. Sie stellt den Konfigurationseintrag und die Bibliotheks-Bytes im Speicher wieder her, sobald Root bestätigt ist, indem geprüft wird, ob /etc/shadow lesbar ist, sodass das Gerät danach unberührt aussieht. Volexity sagt unmissverständlich, dass Ivantis eigenes Integrity Checker Tool das nicht erkennt, weil ICT prüft, was auf der Platte liegt, und das hier die Platte nie berührt. [1] Ein saubener ICT-Lauf heute sagt nichts darüber, ob Stufe drei ein Gerät getroffen hat, weder vor dem 13. Oktober noch danach, denn ein Fix dafür existiert immer noch nicht.
Wie ich das in Code suchen würde, der mit Ivanti nichts zu tun hat
Stufe eins und zwei sind keine ICS-spezifischen Fehler. Es sind zwei Muster, die ich in jedem Reverse-Proxy, jedem API-Gateway oder jedem CGI-artigen Dispatcher prüfe, in jedem Stack. Erstens: Liest die Komponente, die entscheidet, ob Sie hereindürfen, dasselbe Feld wie die Komponente, die entscheidet, welcher Handler läuft? Wenn Autorisierung sich an der URL orientiert und Routing an einem Header, einem Cookie oder einem Query-Parameter, den die Auth-Schicht nie betrachtet, existiert derselbe Fehler auch hier, nur mit anderen Feldnamen.
Zweitens: Existiert etwas, das Ihr Dienst an einen referenzierbaren Pfad schreibt, auch nur kurz, bevor es validiert wurde? Upload-Handler sind das klassische Zuhause dafür, aber es taucht überall auf, wo ein Dienst zuerst persistiert und erst danach prüft: ein Cache-Schreibvorgang, eine temporäre Datei, ein Warteschlangen-Eintrag, den ein Worker abholen kann, bevor ein Validator gelaufen ist. Die beiden Fragen unten sind die, die ich tatsächlich stellen würde, wenn ich einen Handler wie wu.cgi zum ersten Mal lese, und sie brauchen Ivantis Quellcode nicht, um anwendbar zu sein.
// Two questions I ask reading any dispatcher or upload handler,
// independent of what language or framework it's written in.
// 1. Auth-vs-routing mismatch
// - What field decides "is this caller allowed in"? (often the URL)
// - What field decides "which handler actually runs"? (sometimes a
// header, a cookie, a query parameter)
// - If those are two different fields, does anything enforce that
// they agree?
// 2. Persist-before-validate
// - Where does a handler write attacker-influenced content to a
// referenceable path (disk, cache, queue) before fully validating it?
// - Is there a window, however small, where something worse can
// reach what was just written before cleanup or validation runs?Ist das Ihr Notfall? Wenn Sie Connect Secure betreiben, ja
Seien Sie ehrlich darüber, wen das betrifft. Wenn Sie kein Ivanti Connect Secure betreiben, betrifft Sie das nicht. Wenn Sie es tun, gibt es hier keine weiche Landung, wie es sie manchmal bei einem niedrig bewerteten Fund gibt, der zu Dringlichkeit aufgeblasen wird. CVE-2026-104935 ist pre-auth, die eigenen Ausnutzungsdaten lauten bereits auf „aktiv", und Volexitys forensische Rekonstruktion zeigt, dass es eine Appliance traf, auf der bereits jeder verfügbare Patch eingespielt war. [1][2] „Wir sind gepatcht" war für diesen Kunden wahr und hat ihn nicht gerettet.
Drei Dinge bestimmen, wie besorgt Sie sein sollten. Erstens, der Build, der jetzt tatsächlich auf dem Gerät läuft, nicht der, den Ihr Änderungsprotokoll als Ziel nennt: 25.1.1.1 und früher, 25.1.2.2 und früher sowie 22.8R2.4 und früher sind alle noch offen für Stufe eins und zwei. [2] Zweitens, ob der Patch für CVE-2026-104935 das Ende Ihrer Risikobewertung ist oder ihr Anfang. Er schließt die Tür, durch die UTA0576 ging, nicht die, die Volexity drinnen bereits benutzt vorfand. Drittens, ob ein sauberes Integrity-Checker-Tool-Ergebnis als Beweis für keine Kompromittierung herhält, was Volexitys eigene Analyse für diese Kette ausdrücklich verneint. [1]
Die Bewertung 8,6 ist ehrlich in Bezug auf Stufe eins und zwei. Sie sagt nichts über Stufe drei, weil Stufe drei noch keine Bewertung hat, und ein Angreifer, der alle drei verkettet, hört nicht auf, sich darum zu sorgen, nur weil CVSS es tut.
Was Sie prüfen sollten, bevor Sie den Fall schließen
Zuerst patchen, natürlich. Aber Patchen im Nachhinein beantwortet „sind wir ab jetzt sicher", nicht „hat es uns schon getroffen", und das Ausnutzungsfenster dieser Kette erforderte nicht, auf eine Art ungepatcht zu sein, die Sie damals bemerkt hätten. [1]
Volexity verfolgt die Infrastruktur des Angreifers bis zum 30. September 2026 zurück, Ausnutzung beobachtet ab dem 3. Oktober. [1] Wenn die Appliance ab Ende September 2026 mit einem angreifbaren Build im Internet erreichbar war, behandeln Sie sie als möglicherweise kompromittiert und suchen Sie nach Spuren, bevor Sie den Fall schließen. Die Liste unten ist die Reihenfolge, in der ich arbeiten würde.
Before you call this closed:
[ ] Confirm the build actually RUNNING on the appliance, not the one
your change log says. 25.1.1.2 / 25.1.3.0 / 25.1.3.1 / 22.8R2.5 or
later closes CVE-2026-104935 (stages one and two only).
[ ] Pull logs for requests to welcome.cgi where X-DSCGI-PATH does not
match the URL path. That mismatch is this chain's fingerprint.
[ ] Do not close the incident on a clean Integrity Checker Tool run
alone. ICT checks on-disk state; stage three never touches disk.
[ ] If the appliance was internet-facing on a vulnerable build any time
from late September 2026 on, treat it as possibly compromised, not
just unpatched.
[ ] Track Ivanti's advisory for the still-missing privilege-escalation
fix and apply it the day it ships. There is no CVE for it yet.Die CVE ist der leichte Teil
CVE-2026-104935 selbst wird den Nachrichtenzyklus bald verlassen. Die beiden Fehlerklassen dahinter nicht, weil sie nie allein Ivanti gehörten. Eine Autorisierungsprüfung und eine Routing-Entscheidung, die still verschiedenen Feldern derselben Anfrage vertrauen, und ein Schreibpfad, der persistiert, bevor er validiert: Beides finde ich in Web- und API-Code, der nie eine VPN-Appliance berührt hat, meist an Stellen, die niemand zweimal angesehen hat, weil die naheliegende Prüfung, ist dieser Endpunkt authentifiziert, ist dieser Upload-Typ geprüft, bereits bestanden war.
Das ist die Grenze, die ein Secure Code Review nachzeichnen soll: nicht, ob eine Anfrage eine Funktion erreicht hat, sondern ob jedes Tor auf dem Weg dieselben Fakten über diese Anfrage liest. Wenn Sie wollen, dass jemand Ihre Auth- und Routing-Schicht mit genau dieser Frage im Kopf liest, bevor sie zum Zero-Day-Bericht eines anderen wird, ist das genau die Arbeit, die ich mache. Sagen Sie mir, was Sie betreiben, und wir legen den Umfang fest.
Quellen
Soll das auch in Ihrem Produkt geprüft werden?
Security Code-Review