Full Report
Pickle, a serialization format in Python, is actually a small bytecode format that is a small interpreter. It can import modules and execute arbitrary code. Because of this, accepting pickle files as input is an automatic RCE vulnerability in your website. pickle.loads() is the sink to look for. Hugging Face had a vulnerability recently that could have been exploited this way. It's crazy that a serialization format can execute arbitrary code. Imagine is JSON could do this... Pickle has been a bad security mistake for years. So, this post is about trying to fix the security of Pickle in Python! Taint analysis is a strategy to use static code analysis tools to track the flow of untrusted data. In this case, they're trying to use taint analysis into a runtime domain for Python pickle deserialization. The key insight is that if there is a dangerous operation during deserialization, then we can block it. This requires two important things: a hook into dangerous calls and context awareness (are we in a deserialization or not). PEP 578 is Python Audit Hooks. It's a great for auditing Python execution at runtime with custom hooks. Things like os.system can be hooked. This part is super easy once we know what's a well-defined "bad sink". PEP 567 has context variables for thread-local state. This can be placed as taint to specify whether the execution is within Pickle or not. This doesn't work in our case because the taint variable could be modified by the runtime itself. So, it was added at the CPython level, making it impossible to alter. Another alternative was to inspect the call stack. However, this has really bad performance penalties and has zero introspective of C code. Using the audit hooks, it's possible to monitor for security sensitive operations. There is a set of strncmp() with various packages being checked. For instance, os., ctypes. and many others. This blacklist approach works well but broke a bunch of things. The initial version of this blacklist had easy evasion vectors via using global hooks. Many things still had issues, like multiprocessing. Finally, some calls were unaudited for some attributes and not others, making it incomplete. So, back to the drawing board! Almost all sensitive operations appear during the import mechanism. By distinguishing between import related events and other operations, it would create a nice boundary. On the actual execution of bytecode, they were then able to use a whitelist of very specific audit events that have no impact. So, this solves the security problem! This has the limitation that it relies on an audit. They do something unique at the end though: they have a Pickle sandbox with these protections and are asking researchers to escape! I really like this idea, as it gives people a chance to test the security of Pickle. Great article!
Analysis Summary
# Vulnerability: Python Pickle Arbitrary Code Execution (Inherent Design Flaw)
## CVE Details
- **CVE ID**: CVE-2025-9906 (referenced as a recent example in Keras); Pickle exploitation is generally categorized under CWE-502.
- **CVSS Score**: 9.8 (Critical) - *Typical score for remote code execution via deserialization.*
- **CWE**: CWE-502 (Deserialization of Untrusted Data)
## Affected Systems
- **Products**: Python Core Library (`pickle` module), Hugging Face Transformers/Hub, Keras, and any application utilizing `pickle.loads()` or `pickle.load()`.
- **Versions**: All Python versions since inception (25+ years) are inherently vulnerable by design.
- **Configurations**: Any application accepting and deserializing pickle files from untrusted sources (e.g., user uploads, untrusted API responses, or public model repositories).
## Vulnerability Description
The Python `pickle` module implements a stack-based virtual machine (interpreter) to reconstruct Python objects. Unlike data-only formats like JSON, the pickle bytecode includes opcodes (`GLOBAL`, `REDUCE`, etc.) that can import any available module and call any callable with arbitrary arguments.
When `pickle.loads()` is invoked on a malicious payload, the interpreter executes these instructions immediately. This allows an attacker to execute arbitrary system commands (e.g., `os.system('rm -rf /')`) or instantiate malicious objects before the application even begins processing the result.
## Exploitation
- **Status**: Extensively exploited in the wild; PoC available and trivial to create. Recent research (DefCon 2025) demonstrates bypasses of AV/EDR via pickle payloads.
- **Complexity**: Low (Standard payloads can be generated in a few lines of Python).
- **Attack Vector**: Network (Remote execution via uploaded files or API data).
## Impact
- **Confidentiality**: High (Full access to system data and environment variables).
- **Integrity**: High (Ability to modify files, application logic, and system state).
- **Availability**: High (Ability to crash the system or delete critical data).
## Remediation
### Patches
- **Official Status**: There is no "patch" for the standard `pickle` module as it functions as designed.
- **Experimental Fix**: A proposed CPython-level **Context Tainting** implementation (currently a GitHub Pull Request) aims to block unsafe operations during the deserialization runtime.
### Workarounds
- **Switch Formats**: Use `json`, `msgpack`, or `protobuf` for data serialization.
- **Library Alternatives**: For Machine Learning models, use `safetensors` instead of `.bin` or `.pkl` files.
- **Restricted Unpickling**: Override `Pickler.find_class()` to whitelist allowed modules/classes, though this is often bypassed by sophisticated attackers.
## Detection
- **Indicators of Compromise**: Presence of `import os`, `posix.system`, or `__builtin__.eval` within a hex/binary stream sent to a Python web service.
- **Detection Methods**:
- **Static Analysis**: Scrutinize code for `pickle.loads()` or `pickle.load()` (the "sinks").
- **Pickle Scanning**: Tools like `picklescan` can inspect files for common malicious opcodes, though bypasses exist.
- **Runtime Auditing**: Monitor Python Audit Hooks (PEP 578) for suspicious `os.system` or `subprocess` calls originating from within a pickle load operation.
## References
- **Pickle Documentation**: hxxps://docs[.]python[.]org/3/library/pickle[.]html
- **Proposed Fix (GitHub PR)**: hxxps://github[.]com/iyehuda/cpython/pull/1
- **Pickle Escape Challenge**: hxxps://pickleescape[.]xyz/
- **Wiz Research (Hugging Face)**: hxxps://www[.]wiz[.]io/blog/wiz-and-hugging-face-address-risks-to-ai-infrastructure