h5-torch-wrapper-modelscan-bypass-poc / huntr_report_draft.md
captainarp's picture
Add HDF5 TorchModuleWrapper ModelScan bypass PoC
f3aeec0 verified
|
Raw History Blame Contribute Delete
3.5 kB
## Summary
This report demonstrates a ModelScan scanner bypass for an HDF5/Keras-H5 model file containing an unsafe `TorchModuleWrapper` deserialization path.
ModelScan `0.8.8` scans the submitted `.h5` file with `H5LambdaDetectScan` and reports `0 issues` and `0 errors`. The same H5 `model_config` contains a `keras.layers.TorchModuleWrapper` layer with a base64-encoded pickle payload in the `module` field.
Keras itself treats this path as unsafe: default `safe_mode=True` blocks it. When unsafe deserialization is explicitly enabled with `safe_mode=False`, Keras reaches `torch.load(..., weights_only=False)` and the embedded payload writes a harmless marker file.
## Affected Format
HDF5 / Keras H5 `.h5`
## Security Impact
This is a scanner bypass for an unsafe HDF5/Keras-H5 deserialization construct.
If a downstream workflow relies on ModelScan to decide whether a `.h5` model file is safe, this payload is reported clean. If the same model is later loaded in a workflow that enables unsafe Keras deserialization, the embedded `torch.load()` pickle path executes code during model loading.
The PoC payload is intentionally non-destructive. It only writes:
`h5_torch_wrapper_marker.txt`
with contents:
`H5_TORCH_WRAPPER_BYPASS`
## Reproduction Steps
1. Download or clone the public Hugging Face PoC repo.
2. Install dependencies:
`pip install -r requirements.txt`
3. Run the verifier:
`python verify_poc.py --payload poc_h5_torch_wrapper.h5`
4. Observe that ModelScan reports:
`0 issues`
`0 errors`
`total_scanned: 1`
5. Observe that default Keras loading blocks the unsafe path:
`Marker after safe_mode=True: False`
6. Observe that unsafe deserialization reaches the embedded `torch.load()` path and writes the marker:
`Marker after safe_mode=False: True`
`Marker contents: H5_TORCH_WRAPPER_BYPASS`
## Root Cause
ModelScan's H5 scanner checks the H5 `model_config` for layers whose `class_name` is `Lambda`, but it does not detect other unsafe Keras deserialization constructs such as `keras.layers.TorchModuleWrapper`.
Keras treats `TorchModuleWrapper` as unsafe because `TorchModuleWrapper.from_config()` can base64-decode the `module` field and pass it to:
`torch.load(..., weights_only=False)`
This can execute attacker-controlled pickle payloads when unsafe deserialization is enabled.
## Scope Note
This is not a claim that default Keras loading executes the payload. Default `safe_mode=True` blocks the unsafe `TorchModuleWrapper` deserialization path.
The reported issue is that ModelScan reports `0 issues` and `0 errors` for an HDF5/Keras-H5 file containing an unsafe construct that Keras itself treats as dangerous. ModelScan already flags `Lambda` layers in H5 `model_config`, but it does not flag `TorchModuleWrapper`.
## Evidence
PoC file:
`poc_h5_torch_wrapper.h5`
Verification script:
`verify_poc.py`
Scanner output:
`modelscan_result_h5_torch_wrapper.json`
Tested versions:
`modelscan==0.8.8`
`h5py==3.16.0`
`keras==3.15.0`
`tensorflow==2.17.1`
`torch==2.12.1+cpu`
## Suggested Remediation
ModelScan should extend H5 `model_config` scanning beyond `Lambda` layers and flag unsafe Keras deserialization constructs that Keras itself protects with safe mode, including `keras.layers.TorchModuleWrapper`.
For `TorchModuleWrapper`, ModelScan should inspect the `module` field, recognize it as a base64-encoded pickle / torch serialization payload, and either flag it directly or pass it through the existing unsafe pickle scanner.