
Ein offensiver Sicherheitsforscher + eine KI gegen einen frischen n-day: Bau des ersten öffentlichen PoC für CVE-2026-53435 an einem Freitagabend. Rohes 8h20m Log darin.
Erstes öffentliches Proof-of-Concept für CVE-2026-53435, erstellt als nur die Empfehlung existierte und kein PoC veröffentlicht war. Rekonstruiert aus einer vagen einzeiligen Empfehlung in ~8,5 Stunden an einem Freitagabend – nicht durch manuelles Schreiben des Exploits, sondern durch Leiten einer KI durch Sackgassen, Richtungswechsel und Verifikation, bis eine funktionierende Kette herausfiel.

Der PoC erreicht
/etc/passwdauf dem Controller – beliebiges Dateilesen bestätigt.
| CVE | CVE-2026-53435 (Jenkins SECURITY-3707) |
| Klasse | Unsicheres Deserialisieren (ClassFilter-Umgehung via config.xml) |
| Auswirkung (dieser PoC) | Authentifiziertes beliebiges Dateilesen auf dem Controller |
| Betroffen | Jenkins weekly ≤ 2.567, LTS ≤ 2.555.2 |
| Behoben | Jenkins weekly 2.568, LTS 2.555.3 |
| Empfehlung | https://www.jenkins.io/security/advisory/2026-06-10/ (veröffentlicht 2026-06-10) |
| Stand zum Zeitpunkt der Erstellung | Es existierte kein öffentlicher PoC (GitHub PoC-Repos: 0) |
Dies ist ein Experiment, den Prozess zu zeigen, nicht nur das Artefakt.
Jahrelang bestand die Leistung eines offensiven Forschers aus einem einzigen fertigen Exploit – die 8 Stunden Irrwege, die ihn hervorbrachten, verschwanden. Dieses Repository bewahrt die Irrwege. Das vollständige, leicht redigierte Transkript der Zusammenarbeit von Mensch + KI, die eine frische n-day von der Empfehlung bis zum funktionierenden PoC bearbeitet, ist als Anhang beigefügt.
Der Punkt, den es bewusst nicht macht, ist „die KI hat es erledigt.“ Die 8 Stunden sind der Beweis, dass sie es nicht tat. Die KI hat die Kette nie in einem Durchgang erstellt. Was die Arbeit trug, war Domänenexpertise – zu wissen, dass die Empfehlung eine Deserialisierungssenke verbarg, zu wissen, dass man in Jenkins Core-Typen suchen muss, nicht in Plugins, zu wissen, dass eine DescribableList vor dem Patch ihren Elementtyp nicht durchsetzte, und zu wissen, wie man eine Behauptung überprüft, anstatt ihr zu vertrauen. Der Wert des Forschers verschwand nicht; er verlagerte sich vom Tippen des Exploits zum Steuern, Ausdünnen und Validieren.
Diese Verschiebung ist das, was es wert ist, dokumentiert zu werden.
Jenkins schützt die Deserialisierung mit einem benutzerdefinierten ClassFilter, der nur Typen zulässt, die in Jenkins Core oder Plugins definiert sind. CVE-2026-53435 besteht darin, dass dies nicht ausreicht: Ein Angreifer, der ein config.xml POSTen kann, kann Jenkins dazu bringen, einen beliebigen Core/Plugin-Typ in einen Kontext zu deserialisieren, der ihn nie erwartet hat, und dann dieses Objekt über HTTP via Stapler-Routing erreichen.
Dieser PoC platziert ein hudson.Plugin$DummyImpl (ein Jenkins-Core-Typ, passiert also die Standort-Whitelist) in die <properties>-Liste einer ListView – einer DescribableList<ViewProperty>, die vor dem Patch ihren Elementtyp nicht durchsetzt. Das platzierte Objekt trägt baseResourceURL=file:/. Das Routen einer HTTP-Anfrage dorthin liefert dann Dateien direkt vom Dateisystem des Controllers.
Eine generische ysoserial-Kette funktioniert hier nicht – das Gadget muss ein Jenkins/Plugin-eigener Typ sein, um den Filter zu überleben. Diese Einschränkung ist das gesamte Spiel.
Nur gegen Systeme verwenden, die Ihnen gehören oder für die Sie ausdrücklich testen dürfen.
Der Exploit wurde gegen zwei Container mit identischer Konfiguration ausgeführt – anfällig 2.555.2 und gepatcht 2.555.3 – um zu bestätigen, dass er diese CVE ausnutzt und nicht eine nicht zusammenhängende Dateileseprimitive:
Der gepatchte Build akzeptiert die Anfrage, aber der Fix lehnt den platzierten Typ zum Zeitpunkt der Deserialisierung ab, sodass die Eigenschaftenliste leer zurückkommt und nichts zum Routen da ist. Der Fehler trifft genau den Mechanismus, den die Empfehlung beschreibt – genau das macht dies zu einem echten PoC für CVE-2026-53435.
python3 exploit_cve_2026_53435_v2.py <base_url> <user> <pass> <remote_file> [view_name]
# example (against your own lab):
python3 exploit_cve_2026_53435_v2.py http://127.0.0.1:8080 <user> <pass> /etc/passwd
base_url — http://host:port des Ziels (einfaches HTTP; kein https voranstellen, es sei denn, die Instanz terminiert tatsächlich TLS).config.xml (View/Configure) sowie Overall/Read.docker compose -f lab/docker-compose.yml up -d
# vulnerable: http://127.0.0.1:8080 (Jenkins LTS 2.555.2)
# patched: http://127.0.0.1:8081 (Jenkins LTS 2.555.3, negative control)
Das Labor richtet über Groovy-Init eine lokale Benutzerdatenbank ein, sodass der authentifizierte Pfad mit niedrigen Privilegien ohne externen IdP getestet werden kann.
2026-06-12 (Fri) 15:00 → 23:23 ≈ 8h 20m
15:00–15:12 Labor aufgebaut (docker: vuln 2.555.2 / patched 2.555.3) + erste Erkundung
15:12–21:00 Deserialisierungssenke und Gadget-Analyse
(Commons-Collections-Bypass-Versuche fehlgeschlagen → Schwenk
von properties zu actions und zurück)
21:00–22:30 ClassFilter-Analyse + erneute Gadget-Suche, eingeschränkt auf Core-Typen
22:30–23:00 Funktionsfähiger PoC (v1 → v2) + differentielle Verifikation (anfällig vs. gepatcht)
Die meiste Zeit war Analyse, nicht Tipparbeit. Der Exploit selbst entstand in den letzten ~30 Minuten – sobald die richtige Senke und das richtige Gadget bekannt waren.
Erstellt mit Claude Code (Anthropic), interaktiv vom Autor gesteuert:
Das vollständige, leicht redigierte Arbeitstranskript befindet sich in
transcript/ – roh belassen, einschließlich der Sackgassen. (Nur redigiert, um ein paar Zeilen im Ton zu glätten und lokale Netzwerkdetails zu maskieren; nichts Technisches wurde entfernt.)
Nur für autorisierte Sicherheitstests, Forschung und Bildung. Der Autor und die Beitragenden übernehmen keine Haftung für Missbrauch. Die anvisierte Sicherheitslücke ist gepatcht – aktualisieren Sie auf Jenkins LTS 2.555.3 / weekly 2.568 oder neuer.
| 2.555.2 (anfällig) | 2.555.3 (gepatcht) |
|---|
createView | HTTP 200 | HTTP 200 |
Gespeicherte <properties> | <hudson.Plugin_-DummyImpl/> — überlebt | <properties/> — entfernt |
Auslösen → /etc/passwd | root:x:0:0:... — gelesen | leer — blockiert |