Views
No views yet
.keras Archive Duplicate-Entry Scanner Bypass -- PoC.keras model archive is a ZIP with metadata.json, config.json, model.weights.h5. Python zipfile.open(name) / zf.read(name) / zf.extract(name) all resolve duplicate-named entries to the last entry (NameToInfo dict is last-write-wins). infolist() and namelist() return all entries. This asymmetry lets a malicious .keras carry both a benign and a malicious copy of any entry. Scanner walks the archive and sees the benign entry; Keras's _load_model_from_fileobj opens by name and reads the malicious entry.model.weights.h5. No safe_mode precondition. Works against the default safe_mode=True load path. Compounds with the companion Keras H5 layer_names duplicate-attribute finding for full weight-routing control.config.json containing a Lambda layer. Requires safe_mode=False (still common in research/tutorial code).keras/src/saving/saving_lib.py::_load_model_from_fileobj (4 vulnerable call sites: lines 436, 454, 459-460, 473 at HEAD 8f09b27f, confirmed 2026-05-21).build_malicious_keras.py -- self-contained script that produces malicious.keras. Run with python build_malicious_keras.py. Tested on Python 3.10 + h5py (any version).malicious.keras -- the prebuilt artifact. 4 ZIP entries, two of which are duplicate config.json (benign followed by malicious).verify_asymmetry.py -- standalone reproducer (no Keras needed) that demonstrates the scanner-vs-loader visibility split.python verify_asymmetry.py=== SCANNER VIEW (infolist iteration) ===
Total entries: 4
Filenames in order: ['metadata.json', 'config.json', 'config.json', 'model.weights.h5']
config.json count: 2
=== LOADER VIEW (zf.open by name) ===
config = zf.open("config.json").read():
{"module": "keras", "class_name": "Functional", "config": {"layers": ["MALICIOUS_LAMBDA"]}}config.json entries visible in infolist(). zf.open(name) returns ONLY the malicious one. Any scanner relying on by-name retrieval cannot detect the second entry; any tool iterating infolist() and dict-keying by name will silently drop the first entry.1with zipfile.ZipFile(filepath_or_io, "r") as zf:
2 seen = set()
3 for info in zf.infolist():
4 if info.filename in seen:
5 raise ValueError(
6 f"Refusing to load .keras archive with duplicate entry: {info.filename!r}. "
7 "This may indicate a tampered model artifact attempting to bypass scanner inspection.")
8 seen.add(info.filename)
9 # ... existing load logic ...kais113 via huntr.com.