- Lors de l’envoi d’un message dans ChatGPT, le programme Cloudflare Turnstile s’exécute et inspecte non seulement l’empreinte du navigateur, mais aussi l’état de l’application React
- Le programme déchiffré collecte 55 propriétés et applique une procédure de vérification en trois couches : navigateur, réseau et application
- La vérification ne peut être réussie que dans un véritable environnement SPA où le rendu React est terminé, ce qui signifie que les navigateurs headless et les simples requêtes de bot échouent
- L’empreinte collectée est chiffrée puis convertie en
OpenAI-Sentinel-Turnstile-Token, auquel s’ajoutent ensuite les modules Signal Orchestrator et Proof of Work - Comme seuls les serveurs de Cloudflare connaissent la clé de déchiffrement, la frontière de confidentialité est définie par la politique plutôt que par la technique
Analyse du fonctionnement de Cloudflare Turnstile lors de l’envoi d’un message dans ChatGPT
- À chaque envoi de message dans ChatGPT, le programme Cloudflare Turnstile s’exécute automatiquement dans le navigateur
- L’analyse de 377 programmes Turnstile déchiffrés à partir du trafic réseau montre qu’il ne se limite pas à une collecte classique d’empreinte navigateur, mais inspecte aussi l’état de l’application React
- Les bots qui ne font que falsifier l’empreinte du navigateur ne passent pas la vérification ; il faut rendre complètement la SPA (application monopage) de ChatGPT pour la réussir
Structure de chiffrement et processus de déchiffrement
- Le bytecode Turnstile est transmis dans le champ
turnstile.dxde la réponse serveur et il est chiffré à chaque requête sous la forme d’une chaîne base64 de 28 000 caractères- La couche de chiffrement externe peut être déchiffrée par une opération XOR avec le jeton
p, les deux valeurs étant échangées dans la même requête HTTP - Le résultat déchiffré est un bytecode JSON composé de 89 instructions de VM
- La couche de chiffrement externe peut être déchiffrée par une opération XOR avec le jeton
- À l’intérieur se trouve un blob chiffré supplémentaire de 19 KB, lui aussi chiffré avec une autre clé XOR
- La clé est incluse dans le bytecode sous la forme d’une valeur littérale flottante (par ex.
97.35), générée par le serveur puis envoyée au navigateur - Sur 50 requêtes, un déchiffrement valide en JSON a été confirmé selon le même procédé
- La clé est incluse dans le bytecode sous la forme d’une valeur littérale flottante (par ex.
- La procédure complète de déchiffrement se compose des 5 étapes suivantes
- Lire le jeton
pdans la requête - Lire
turnstile.dxdans la réponse XOR(base64decode(dx), p)→ génération du bytecode externe- Extraire la dernière valeur comme clé depuis l’instruction à 5 arguments située après le blob de 19 KB
XOR(base64decode(blob), str(key))→ déchiffrement du programme interne (417 à 580 instructions)
- Lire le jeton
Éléments inspectés par le programme déchiffré
- Le programme interne s’exécute sur une VM personnalisée avec 28 instructions (opcodes), et les adresses des registres flottants changent aléatoirement à chaque requête
- Il collecte au total 55 propriétés, identiques dans les 377 échantillons
-
Couche 1 : empreinte du navigateur
- 8 propriétés liées à WebGL :
UNMASKED_VENDOR_WEBGL,UNMASKED_RENDERER_WEBGL,WEBGL_debug_renderer_info, etc. - 8 informations d’écran :
colorDepth,pixelDepth,width,height,availWidth,availHeight,availLeft,availTop - 5 éléments matériels :
hardwareConcurrency,deviceMemory,maxTouchPoints,platform,vendor - 4 mesures de police : création d’un div caché puis mesure de la taille de rendu via
fontFamily,fontSize,getBoundingClientRect,innerText - 8 éléments d’exploration du DOM :
createElement,appendChild,removeChild,style,position,visibility,ariaHidden, etc. - 5 éléments de stockage :
storage,quota,estimate,setItem,usage- Les résultats sont stockés dans
localStoragesous la clé6f376b6560133c2cafin de persister entre les rechargements de page
- Les résultats sont stockés dans
- 8 propriétés liées à WebGL :
-
Couche 2 : réseau Cloudflare
- 5 en-têtes edge :
cfIpCity,cfIpLatitude,cfIpLongitude,cfConnectingIp,userRegion - Ces valeurs n’existent qu’au sein du réseau Cloudflare ; les bots accédant directement au serveur d’origine obtiennent donc des champs absents ou incohérents
- 5 en-têtes edge :
-
Couche 3 : état de l’application
- 3 structures internes React :
__reactRouterContext,loaderData,clientBootstrap - Ces éléments n’existent que lorsque l’application React de ChatGPT a été entièrement rendue et que l’hydratation SSR est terminée
- Les navigateurs headless qui ne chargent que le HTML ou n’exécutent pas les bundles JS, ainsi que les frameworks de bots qui n’exécutent pas réellement React, échouent
- 3 structures internes React :
Processus de génération du jeton
- Après la collecte des 55 propriétés, le programme déchiffre un blob chiffré de 116 octets puis exécute 4 instructions finales
JSON.stringify(fingerprint)→store→XOR(json, key)→RESOLVE- La valeur obtenue est convertie en en-tête
OpenAI-Sentinel-Turnstile-Tokenet incluse dans toutes les requêtes de conversation
Composants supplémentaires de Sentinel
- En plus de Turnstile, deux modules de vérification supplémentaires sont présents
-
Signal Orchestrator
- Composé de 271 instructions
- Installe des écouteurs d’événements
keydown,pointermove,click,scroll,paste,wheel - Suit 36 propriétés
window.__oai_so_*afin de surveiller le timing de frappe, la vitesse de la souris, les schémas de défilement, le temps d’inactivité et les événements de collage - Il joue le rôle d’une couche biométrique comportementale en plus de la collecte d’empreinte
-
Proof of Work
- Basé sur une empreinte de 25 champs + SHA-256 hashcash
- La difficulté est un nombre aléatoire uniforme dans une plage de 400K à 500K ; 72 % sont résolus en 5 ms ou moins
- Il inclut 7 indicateurs binaires de détection :
ai,createPRNG,cache,solana,dump,InstallTrigger,data(tous à 0 dans 100 échantillons) - Il ajoute un coût de calcul, mais ne constitue pas le principal moyen de défense
Qui peut déchiffrer le jeton et implications de sécurité
- Comme la clé XOR du programme interne est générée par le serveur puis intégrée au bytecode, seul le serveur qui a généré
turnstile.dxconnaît cette clé - La frontière de confidentialité entre l’utilisateur et l’opérateur du système est définie par une décision de politique, et non par une contrainte cryptographique
- L’objectif de l’obfuscation est de
- masquer les éléments collectés contre l’analyse statique
- empêcher l’exploitant du site (OpenAI) de lire directement les valeurs brutes de l’empreinte
- rendre chaque jeton unique afin d’éviter la réutilisation (replay)
- rendre difficile la détection externe des changements opérés par Cloudflare sur les éléments inspectés
- Cependant, le chiffrement repose sur une opération XOR avec la clé dans le même flux de données, ce qui n’est qu’une obfuscation destinée à compliquer l’analyse
Statistiques de collecte et d’analyse
| Élément | Valeur |
|---|---|
| Programmes déchiffrés | 377/377 (100 %) |
| Utilisateurs uniques observés | 32 |
| Nombre de propriétés par programme | 55 (identiques pour tous) |
| Nombre d’instructions | 417–580 (moyenne 480) |
| Clés XOR (50 échantillons) | 41 |
| Propriétés de Signal Orchestrator | 36 |
| Champs de Proof of Work | 25 |
| Temps de résolution du PoW | 72 % en 5 ms ou moins |
Méthodologie d’analyse
- Seul du trafic collecté selon une procédure légale a été utilisé
- Aucune donnée personnelle d’utilisateurs n’a été publiée
- Tout le trafic a été observé avec le consentement des participants
- Le SDK Sentinel (
sdk.js, 1 411 lignes) a fait l’objet d’une désobfuscation manuelle et d’un déchiffrement hors ligne - Le déchiffrement a été réalisé hors ligne en Python
Aucun commentaire pour le moment.