Instructions to use th3-j0k3r/modelscan-keras-nested-lambda-bypass with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Keras
How to use th3-j0k3r/modelscan-keras-nested-lambda-bypass with Keras:
# !pip install -U keras tensorflow huggingface_hub # Keras needs TensorFlow installed to read "hf://" paths, so the tensorflow backend is selected here; # "jax" and "torch" also work for computation once TensorFlow is installed. import os os.environ["KERAS_BACKEND"] = "tensorflow" import keras model = keras.saving.load_model("hf://th3-j0k3r/modelscan-keras-nested-lambda-bypass") - Notebooks
- Google Colab
- Kaggle
Upload README.md with huggingface_hub
Browse files
README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ModelScan Bypass — Arbitrary Code Execution in `.keras` via Nested Lambda Layer
|
| 2 |
+
|
| 3 |
+
Proof-of-concept for a **Huntr Model File Format** report.
|
| 4 |
+
|
| 5 |
+
`model.keras` runs code on `keras.saving.load_model(..., safe_mode=False)` while
|
| 6 |
+
**ModelScan reports it clean**. The malicious Lambda layer is hidden **one level deep inside a
|
| 7 |
+
nested sub-model**. ModelScan's `KerasLambdaDetectScan` only inspects the flat, top-level
|
| 8 |
+
`config.layers` list (and matches `class_name == "Lambda"` exactly), so it never sees the
|
| 9 |
+
nested Lambda — but Keras deserializes sub-models recursively and executes it on load.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
- `model.keras` — the PoC model file (benign payload: writes `keras_poc_executed.txt`)
|
| 13 |
+
- `exploit.py` — builds a flat model (detected) and the nested model (missed), scans + loads both
|
| 14 |
+
- `README.md` — this file
|
| 15 |
+
|
| 16 |
+
## Reproduce
|
| 17 |
+
```bash
|
| 18 |
+
pip install 'modelscan[tensorflow]' tensorflow keras
|
| 19 |
+
# 1) scanner says it is safe:
|
| 20 |
+
modelscan -p model.keras # -> No issues found! 🎉
|
| 21 |
+
# 2) loading it executes code:
|
| 22 |
+
python -c "import keras; keras.saving.load_model('model.keras', safe_mode=False, compile=False)"
|
| 23 |
+
ls keras_poc_executed.txt # marker proves code ran on load
|
| 24 |
+
# or run the full demo (flat=detected, nested=bypass):
|
| 25 |
+
python exploit.py # -> No issues found (BYPASS) + CODE EXECUTED
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
## Why it works
|
| 29 |
+
`modelscan/scanners/keras/scan.py` (`_get_keras_operator_names`):
|
| 30 |
+
```python
|
| 31 |
+
lambda_layers = [
|
| 32 |
+
layer.get("config", {}).get("function", {})
|
| 33 |
+
for layer in model_config_data.get("config", {}).get("layers", {}) # TOP LEVEL ONLY
|
| 34 |
+
if layer.get("class_name", {}) == "Lambda" # exact match
|
| 35 |
+
]
|
| 36 |
+
```
|
| 37 |
+
It iterates only the outer `config.layers` and never recurses. A Lambda inside a sub-model
|
| 38 |
+
(top-level `class_name` is `Functional`, not `Lambda`) is invisible to the scan.
|
| 39 |
+
|
| 40 |
+
## Impact
|
| 41 |
+
ModelScan is used to gate untrusted models in MLOps pipelines / model hubs. This file passes
|
| 42 |
+
the scan as clean yet achieves arbitrary code execution on load — defeating the control.
|
| 43 |
+
Verified on modelscan 0.8.8 / Keras 3.14.1 / TensorFlow 2.21.
|
| 44 |
+
|
| 45 |
+
The payload here is benign (writes a marker file). Swap the command inside the Lambda in
|
| 46 |
+
`exploit.py` to confirm real command execution.
|