mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['', 'src/generic-methodologies-and-resources/python/keras-mo
This commit is contained in:
parent
418ab41cef
commit
2abaa79e39
@ -2,18 +2,18 @@
|
|||||||
|
|
||||||
{{#include ../../banners/hacktricks-training.md}}
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
|
|
||||||
Cette page résume les techniques d'exploitation pratiques contre le pipeline de désérialisation des modèles Keras, explique les détails internes du format .keras et la surface d'attaque, et fournit un kit d'outils pour les chercheurs afin de trouver des vulnérabilités de fichiers de modèle (MFVs) et des gadgets post-correction.
|
Cette page résume des techniques d'exploitation pratiques contre le pipeline de désérialisation de modèles Keras, explique les internals du format natif .keras et sa surface d'attaque, et fournit une boîte à outils pour chercheurs pour trouver Model File Vulnerabilities (MFVs) et post-fix gadgets.
|
||||||
|
|
||||||
## Détails internes du format de modèle .keras
|
## Internes du format de modèle .keras
|
||||||
|
|
||||||
Un fichier .keras est une archive ZIP contenant au moins :
|
Un fichier .keras est une archive ZIP contenant au minimum :
|
||||||
- metadata.json – informations génériques (par exemple, version de Keras)
|
- metadata.json – informations génériques (par ex., version de Keras)
|
||||||
- config.json – architecture du modèle (surface d'attaque principale)
|
- config.json – architecture du modèle (surface d'attaque principale)
|
||||||
- model.weights.h5 – poids en HDF5
|
- model.weights.h5 – poids en HDF5
|
||||||
|
|
||||||
Le config.json entraîne une désérialisation récursive : Keras importe des modules, résout des classes/fonctions et reconstruit des couches/objets à partir de dictionnaires contrôlés par l'attaquant.
|
Le config.json pilote une désérialisation récursive : Keras importe des modules, résout les classes/fonctions et reconstruit les layers/objets à partir de dictionnaires contrôlés par l'attaquant.
|
||||||
|
|
||||||
Extrait d'exemple pour un objet de couche Dense :
|
Example snippet for a Dense layer object:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"module": "keras.layers",
|
"module": "keras.layers",
|
||||||
@ -31,22 +31,22 @@ Extrait d'exemple pour un objet de couche Dense :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
La désérialisation effectue :
|
La désérialisation effectue:
|
||||||
- Importation de modules et résolution de symboles à partir des clés module/class_name
|
- Importation de modules et résolution de symboles à partir des clés module/class_name
|
||||||
- invocation de from_config(...) ou du constructeur avec des kwargs contrôlés par l'attaquant
|
- Appel de from_config(...) ou du constructeur avec des kwargs contrôlés par l'attaquant
|
||||||
- Récursion dans des objets imbriqués (activations, initialisateurs, contraintes, etc.)
|
- Récursion dans les objets imbriqués (activations, initializers, constraints, etc.)
|
||||||
|
|
||||||
Historiquement, cela a exposé trois primitives à un attaquant créant config.json :
|
Historiquement, cela exposait trois primitives à un attaquant forgeant config.json:
|
||||||
- Contrôle des modules importés
|
- Contrôle des modules importés
|
||||||
- Contrôle des classes/fonctions résolues
|
- Contrôle des classes/fonctions résolues
|
||||||
- Contrôle des kwargs passés dans les constructeurs/from_config
|
- Contrôle des kwargs passés aux constructeurs/from_config
|
||||||
|
|
||||||
## CVE-2024-3660 – Exécution de code à distance par bytecode de couche Lambda
|
## CVE-2024-3660 – Lambda-layer bytecode RCE
|
||||||
|
|
||||||
Cause racine :
|
Cause racine:
|
||||||
- Lambda.from_config() utilisait python_utils.func_load(...) qui décode en base64 et appelle marshal.loads() sur des octets de l'attaquant ; la désérialisation Python peut exécuter du code.
|
- Lambda.from_config() utilisait python_utils.func_load(...) qui décodait en base64 et appelait marshal.loads() sur des octets fournis par l'attaquant; la désérialisation Python peut exécuter du code.
|
||||||
|
|
||||||
Idée d'exploitation (charge utile simplifiée dans config.json) :
|
Idée d'exploit (payload simplifié dans config.json):
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"module": "keras.layers",
|
"module": "keras.layers",
|
||||||
@ -60,19 +60,19 @@ Idée d'exploitation (charge utile simplifiée dans config.json) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Mitigation:
|
Atténuation :
|
||||||
- Keras impose safe_mode=True par défaut. Les fonctions Python sérialisées dans Lambda sont bloquées à moins qu'un utilisateur ne choisisse explicitement de désactiver avec safe_mode=False.
|
- Keras impose safe_mode=True par défaut. Les fonctions Python sérialisées dans Lambda sont bloquées sauf si un utilisateur choisit explicitement safe_mode=False.
|
||||||
|
|
||||||
Notes:
|
Remarques :
|
||||||
- Les formats hérités (anciens enregistrements HDF5) ou les anciennes bases de code peuvent ne pas appliquer les vérifications modernes, donc les attaques de style "downgrade" peuvent toujours s'appliquer lorsque les victimes utilisent des chargeurs plus anciens.
|
- Formats legacy (anciennes sauvegardes HDF5) ou bases de code plus anciennes peuvent ne pas appliquer les contrôles modernes, donc des attaques de type “downgrade” peuvent encore s'appliquer lorsque les victimes utilisent d'anciens loaders.
|
||||||
|
|
||||||
## CVE-2025-1550 – Importation de module arbitraire dans Keras ≤ 3.8
|
## CVE-2025-1550 – Import arbitraire de module dans Keras ≤ 3.8
|
||||||
|
|
||||||
Root cause:
|
Cause racine :
|
||||||
- _retrieve_class_or_fn utilisait importlib.import_module() sans restriction avec des chaînes de module contrôlées par l'attaquant provenant de config.json.
|
- _retrieve_class_or_fn utilisait importlib.import_module() sans restriction avec des chaînes de module contrôlées par l'attaquant provenant de config.json.
|
||||||
- Impact : Importation arbitraire de tout module installé (ou module planté par l'attaquant sur sys.path). Le code s'exécute au moment de l'importation, puis la construction de l'objet se produit avec des kwargs de l'attaquant.
|
- Impact : Import arbitraire de n'importe quel module installé (ou d'un module placé par l'attaquant sur sys.path). Le code s'exécute à l'import, puis l'objet est construit avec des kwargs fournis par l'attaquant.
|
||||||
|
|
||||||
Exploit idea:
|
Idée d'exploit :
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"module": "maliciouspkg",
|
"module": "maliciouspkg",
|
||||||
@ -80,16 +80,16 @@ Exploit idea:
|
|||||||
"config": {"arg": "val"}
|
"config": {"arg": "val"}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Améliorations de la sécurité (Keras ≥ 3.9) :
|
Améliorations de sécurité (Keras ≥ 3.9) :
|
||||||
- Liste blanche des modules : importations restreintes aux modules de l'écosystème officiel : keras, keras_hub, keras_cv, keras_nlp
|
- Module allowlist: les imports sont restreints aux modules officiels de l'écosystème : keras, keras_hub, keras_cv, keras_nlp
|
||||||
- Mode sécurisé par défaut : safe_mode=True bloque le chargement de fonctions sérialisées Lambda non sécurisées
|
- Safe mode default: safe_mode=True bloque le chargement de fonctions sérialisées Lambda non sécurisées
|
||||||
- Vérification de type de base : les objets désérialisés doivent correspondre aux types attendus
|
- Basic type checking: les objets désérialisés doivent correspondre aux types attendus
|
||||||
|
|
||||||
## Surface de gadgets post-correction à l'intérieur de la liste blanche
|
## Surface de gadgets post-fix dans l'allowlist
|
||||||
|
|
||||||
Même avec la liste blanche et le mode sécurisé, une large surface reste parmi les appelables Keras autorisés. Par exemple, keras.utils.get_file peut télécharger des URL arbitraires vers des emplacements sélectionnables par l'utilisateur.
|
Même avec l'allowlisting et le safe mode, une large surface reste présente parmi les callables Keras autorisés. Par exemple, keras.utils.get_file peut télécharger des URLs arbitraires vers des emplacements choisis par l'utilisateur.
|
||||||
|
|
||||||
Gadget via Lambda qui référence une fonction autorisée (pas de bytecode Python sérialisé) :
|
Gadget via Lambda qui référence une fonction autorisée (et non du bytecode Python sérialisé) :
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"module": "keras.layers",
|
"module": "keras.layers",
|
||||||
@ -105,19 +105,19 @@ Gadget via Lambda qui référence une fonction autorisée (pas de bytecode Pytho
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Limitation importante :
|
Important limitation:
|
||||||
- Lambda.call() ajoute le tenseur d'entrée comme le premier argument positionnel lors de l'invocation de l'appelable cible. Les gadgets choisis doivent tolérer un argument positionnel supplémentaire (ou accepter *args/**kwargs). Cela limite les fonctions viables.
|
- Lambda.call() préfixe le tenseur d'entrée comme premier argument positionnel lors de l'invocation du callable cible. Les gadgets choisis doivent tolérer un argument positionnel supplémentaire (ou accepter *args/**kwargs). Cela contraint les fonctions viables.
|
||||||
|
|
||||||
Impacts potentiels des gadgets autorisés :
|
Potential impacts of allowlisted gadgets:
|
||||||
- Téléchargement/écriture arbitraire (plantation de chemin, empoisonnement de configuration)
|
- Téléchargement/écriture arbitraire (path planting, config poisoning)
|
||||||
- Rappels réseau/effets similaires à SSRF selon l'environnement
|
- Callbacks réseau/effets de type SSRF selon l'environnement
|
||||||
- Chaînage vers l'exécution de code si les chemins écrits sont ensuite importés/exécutés ou ajoutés à PYTHONPATH, ou si un emplacement d'exécution sur écriture accessible existe
|
- Chaînage vers l'exécution de code si les chemins écrits sont ensuite importés/exécutés ou ajoutés à PYTHONPATH, ou si un emplacement exécutable-à-l'écriture existe
|
||||||
|
|
||||||
## Boîte à outils du chercheur
|
## Researcher toolkit
|
||||||
|
|
||||||
1) Découverte systématique de gadgets dans les modules autorisés
|
1) Systematic gadget discovery in allowed modules
|
||||||
|
|
||||||
Énumérer les appelables candidats à travers keras, keras_nlp, keras_cv, keras_hub et prioriser ceux ayant des effets secondaires sur les fichiers/réseaux/processus/environnement.
|
Énumérer les callables candidats dans keras, keras_nlp, keras_cv, keras_hub et prioriser ceux ayant des effets secondaires sur les fichiers/le réseau/les processus/l'environnement.
|
||||||
```python
|
```python
|
||||||
import importlib, inspect, pkgutil
|
import importlib, inspect, pkgutil
|
||||||
|
|
||||||
@ -160,9 +160,9 @@ candidates.append(text)
|
|||||||
|
|
||||||
print("\n".join(sorted(candidates)[:200]))
|
print("\n".join(sorted(candidates)[:200]))
|
||||||
```
|
```
|
||||||
2) Test de désérialisation directe (aucune archive .keras nécessaire)
|
2) Tests de désérialisation directs (aucune archive .keras nécessaire)
|
||||||
|
|
||||||
Alimentez des dictionnaires conçus directement dans les désérialiseurs Keras pour apprendre les paramètres acceptés et observer les effets secondaires.
|
Injectez des dicts spécialement conçus directement dans les désérialiseurs Keras pour découvrir les paramètres acceptés et observer les effets secondaires.
|
||||||
```python
|
```python
|
||||||
from keras import layers
|
from keras import layers
|
||||||
|
|
||||||
@ -178,22 +178,63 @@ cfg = {
|
|||||||
|
|
||||||
layer = layers.deserialize(cfg, safe_mode=True) # Observe behavior
|
layer = layers.deserialize(cfg, safe_mode=True) # Observe behavior
|
||||||
```
|
```
|
||||||
3) Probe croisée des versions et formats
|
3) Tests inter-versions et formats
|
||||||
|
|
||||||
Keras existe dans plusieurs bases de code/époques avec différentes protections et formats :
|
Keras existe dans plusieurs bases de code/époques avec des garde-fous et formats différents :
|
||||||
- Keras intégré à TensorFlow : tensorflow/python/keras (héritage, prévu pour suppression)
|
- TensorFlow built-in Keras: tensorflow/python/keras (legacy, prévu pour suppression)
|
||||||
- tf-keras : maintenu séparément
|
- tf-keras: maintenu séparément
|
||||||
- Keras 3 multi-backend (officiel) : introduction du .keras natif
|
- Multi-backend Keras 3 (official): a introduit le format natif .keras
|
||||||
|
|
||||||
Répétez les tests à travers les bases de code et les formats (.keras vs HDF5 hérité) pour découvrir des régressions ou des protections manquantes.
|
Répétez les tests à travers les bases de code et formats (.keras vs legacy HDF5) pour détecter des régressions ou l'absence de garde-fous.
|
||||||
|
|
||||||
## Recommandations défensives
|
## Recommandations défensives
|
||||||
|
|
||||||
- Traitez les fichiers de modèle comme des entrées non fiables. Chargez uniquement des modèles provenant de sources de confiance.
|
- Considérez les fichiers de modèle comme des entrées non fiables. Ne chargez des modèles que depuis des sources de confiance.
|
||||||
- Gardez Keras à jour ; utilisez Keras ≥ 3.9 pour bénéficier de la liste blanche et des vérifications de type.
|
- Maintenez Keras à jour ; utilisez Keras ≥ 3.9 pour bénéficier de l'allowlisting et des vérifications de type.
|
||||||
- Ne définissez pas safe_mode=False lors du chargement des modèles à moins de faire entièrement confiance au fichier.
|
- Ne réglez pas safe_mode=False lors du chargement des modèles, sauf si vous faites entièrement confiance au fichier.
|
||||||
- Envisagez d'exécuter la désérialisation dans un environnement isolé, avec le moins de privilèges possible, sans sortie réseau et avec un accès au système de fichiers restreint.
|
- Envisagez d'exécuter la désérialisation dans un environnement sandboxé, avec les privilèges minimaux, sans sortie réseau et avec un accès au système de fichiers restreint.
|
||||||
- Appliquez des listes blanches/signatures pour les sources de modèles et la vérification d'intégrité lorsque cela est possible.
|
- Appliquez des allowlists/signatures pour les sources de modèles et vérifiez l'intégrité lorsque c'est possible.
|
||||||
|
|
||||||
|
## ML pickle import allowlisting for AI/ML models (Fickling)
|
||||||
|
|
||||||
|
De nombreux formats de modèles AI/ML (PyTorch .pt/.pth/.ckpt, joblib/scikit-learn, anciens artefacts TensorFlow, etc.) embarquent des données Python pickle. Les attaquants abusent régulièrement des imports GLOBAL de pickle et des constructeurs d'objets pour obtenir un RCE ou remplacer des modèles lors du chargement. Les scanners basés sur des listes noires manquent souvent des imports dangereux nouveaux ou non listés.
|
||||||
|
|
||||||
|
Une défense pratique en mode fail-closed consiste à intercepter le désérialiseur pickle de Python et à n'autoriser qu'un ensemble révisé d'importations liées au ML non dangereuses lors de l'unpickling. Trail of Bits’ Fickling implémente cette politique et fournit une allowlist d'importations ML triée, construite à partir de milliers de pickles publics Hugging Face.
|
||||||
|
|
||||||
|
Modèle de sécurité pour les imports “safe” (intuitions distillées de la recherche et de la pratique) : les symboles importés utilisés par un pickle doivent simultanément :
|
||||||
|
- Ne pas exécuter de code ni provoquer d'exécution (pas d'objets code compilé/source, pas d'exécution de shell, pas de hooks, etc.)
|
||||||
|
- Ne pas lire/écrire des attributs ou éléments arbitraires
|
||||||
|
- Ne pas importer ni obtenir des références à d'autres objets Python depuis la VM pickle
|
||||||
|
- Ne pas déclencher de désérialiseurs secondaires (p.ex., marshal, nested pickle), même indirectement
|
||||||
|
|
||||||
|
Activez les protections de Fickling le plus tôt possible au démarrage du processus afin que tous les chargements de pickle effectués par les frameworks (torch.load, joblib.load, etc.) soient contrôlés :
|
||||||
|
```python
|
||||||
|
import fickling
|
||||||
|
# Sets global hooks on the stdlib pickle module
|
||||||
|
fickling.hook.activate_safe_ml_environment()
|
||||||
|
```
|
||||||
|
Conseils opérationnels :
|
||||||
|
- Vous pouvez temporairement désactiver/réactiver les hooks là où c'est nécessaire :
|
||||||
|
```python
|
||||||
|
fickling.hook.deactivate_safe_ml_environment()
|
||||||
|
# ... load fully trusted files only ...
|
||||||
|
fickling.hook.activate_safe_ml_environment()
|
||||||
|
```
|
||||||
|
- Si un modèle connu et fiable est bloqué, étendez l'allowlist pour votre environnement après avoir examiné les symboles :
|
||||||
|
```python
|
||||||
|
fickling.hook.activate_safe_ml_environment(also_allow=[
|
||||||
|
"package.subpackage.safe_symbol",
|
||||||
|
"another.safe.import",
|
||||||
|
])
|
||||||
|
```
|
||||||
|
- Fickling expose également des gardes d'exécution génériques si vous préférez un contrôle plus granulaire :
|
||||||
|
- fickling.always_check_safety() pour appliquer des vérifications pour tous les pickle.load()
|
||||||
|
- with fickling.check_safety(): pour une application limitée dans une portée
|
||||||
|
- fickling.load(path) / fickling.is_likely_safe(path) pour des vérifications ponctuelles
|
||||||
|
|
||||||
|
- Préférez des formats de modèle non-pickle lorsque possible (p.ex., SafeTensors). Si vous devez accepter des pickle, exécutez les loaders selon le principe du moindre privilège sans sortie réseau et appliquez l'allowlist.
|
||||||
|
|
||||||
|
Cette stratégie allowlist-first bloque de manière démontrable les chemins d'exploitation courants des pickle en ML tout en maintenant une compatibilité élevée. Dans le benchmark de ToB, Fickling a signalé 100% des fichiers malveillants synthétiques et a autorisé ~99% des fichiers propres provenant des principaux repos Hugging Face.
|
||||||
|
|
||||||
## Références
|
## Références
|
||||||
|
|
||||||
@ -203,5 +244,11 @@ Répétez les tests à travers les bases de code et les formats (.keras vs HDF5
|
|||||||
- [CVE-2025-1550 – Keras arbitrary module import (≤ 3.8)](https://nvd.nist.gov/vuln/detail/CVE-2025-1550)
|
- [CVE-2025-1550 – Keras arbitrary module import (≤ 3.8)](https://nvd.nist.gov/vuln/detail/CVE-2025-1550)
|
||||||
- [huntr report – arbitrary import #1](https://huntr.com/bounties/135d5dcd-f05f-439f-8d8f-b21fdf171f3e)
|
- [huntr report – arbitrary import #1](https://huntr.com/bounties/135d5dcd-f05f-439f-8d8f-b21fdf171f3e)
|
||||||
- [huntr report – arbitrary import #2](https://huntr.com/bounties/6fcca09c-8c98-4bc5-b32c-e883ab3e4ae3)
|
- [huntr report – arbitrary import #2](https://huntr.com/bounties/6fcca09c-8c98-4bc5-b32c-e883ab3e4ae3)
|
||||||
|
- [Trail of Bits blog – Fickling’s new AI/ML pickle file scanner](https://blog.trailofbits.com/2025/09/16/ficklings-new-ai/ml-pickle-file-scanner/)
|
||||||
|
- [Fickling – Securing AI/ML environments (README)](https://github.com/trailofbits/fickling#securing-aiml-environments)
|
||||||
|
- [Fickling pickle scanning benchmark corpus](https://github.com/trailofbits/fickling/tree/master/pickle_scanning_benchmark)
|
||||||
|
- [Picklescan](https://github.com/mmaitre314/picklescan), [ModelScan](https://github.com/protectai/modelscan), [model-unpickler](https://github.com/goeckslab/model-unpickler)
|
||||||
|
- [Sleepy Pickle attacks background](https://blog.trailofbits.com/2024/06/11/exploiting-ml-models-with-pickle-file-attacks-part-1/)
|
||||||
|
- [SafeTensors project](https://github.com/safetensors/safetensors)
|
||||||
|
|
||||||
{{#include ../../banners/hacktricks-training.md}}
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user