| --- |
| license: mit |
| tags: |
| - security |
| - poc |
| --- |
| |
| # torch.package `module_allowed` security callback bypass -> RCE |
| |
| PoC for a huntr Model File Vulnerability (MFV) report against the **PyTorch Package (.pt)** |
| format (`torch.package`). This is a separate, distinct root cause from the earlier |
| `torch-package-interned-module-rce-poc` submission (interned-module `exec()`). |
| |
| ## The bug |
| |
| `PackageImporter.__init__` calls `module_allowed(extern_module)` for every name in |
| `self.extern_modules` (read from `.data/extern_modules` in the archive), and raises if |
| it returns `False`. This is the *only* documented trust-boundary parameter the API |
| offers -- the docstring says it exists so callers can "ensure packages loaded do not |
| depend on modules that the server does not support." |
| |
| Separately, `_load_module()`'s fallback path -- reached whenever the module tree lookup |
| misses -- checks a hardcoded `IMPLICIT_IMPORT_ALLOWLIST` (`numpy`, `numpy.core`, |
| `numpy.core._multiarray_umath`, `builtins`) and, if the name is on it, does |
| `importlib.import_module(name)` immediately. It never calls `module_allowed`. |
| |
| If an attacker crafts a `.pt` archive where `.data/extern_modules` simply doesn't |
| mention `builtins` (trivial -- just don't write that line, no need to go through the |
| real `PackageExporter` API at all for the final artifact), `module_allowed('builtins')` |
| is never invoked. `find_class('builtins', 'eval')` still resolves successfully through |
| the `IMPLICIT_IMPORT_ALLOWLIST` fallback. A developer who follows the docs and passes |
| `module_allowed=lambda m: False` to lock everything down is still fully exploitable. |
|
|
| ## Files |
|
|
| - `reproduce.py` -- full repro: builds a package with `Exploit.__reduce__` calling |
| `eval(...)`, strips `builtins` out of `.data/extern_modules`, then loads it through a |
| `PackageImporter` locked down with `module_allowed=lambda m: False`. Prints whether |
| `module_allowed` got called (it doesn't) and creates `/tmp/torch_package_builtins_bypass_pwned`. |
| - `build_payload.py` / `malicious_builtins_stripped.pt` -- the payload build script and |
| a pre-built copy of the malicious archive. |
| - `trigger_stripped.py` -- just the load-and-verify half of `reproduce.py`, against the |
| pre-built `.pt` file. |
|
|
| ## Reproduce |
|
|
| ```bash |
| pip install torch |
| python3 reproduce.py |
| ls -la /tmp/torch_package_builtins_bypass_pwned # created despite module_allowed=False |
| ``` |
|
|
| Full report: huntr.com MFV submission (PyTorch Package format). |
|
|