GHSA-Q5QQ-MVFM-J35X
Vulnerability from github – Published: 2026-01-09 21:12 – Updated: 2026-01-09 21:12Fickling's assessment
ctypes, importlib, runpy, code and multiprocessing were added the list of unsafe imports (https://github.com/trailofbits/fickling/commit/9a2b3f89bd0598b528d62c10a64c1986fcb09f66, https://github.com/trailofbits/fickling/commit/eb299b453342f1931c787bcb3bc33f3a03a173f9, https://github.com/trailofbits/fickling/commit/29d5545e74b07766892c1f0461b801afccee4f91, https://github.com/trailofbits/fickling/commit/b793563e60a5e039c5837b09d7f4f6b92e6040d1, https://github.com/trailofbits/fickling/commit/b793563e60a5e039c5837b09d7f4f6b92e6040d1).
Original report
Summary
The unsafe_imports() method in Fickling's static analyzer fails to flag several high-risk Python modules that can be used for arbitrary code execution. Malicious pickles importing these modules will not be detected as unsafe, allowing attackers to bypass Fickling's primary static safety checks.
Details
In fickling/fickle.py lines 866-884, the unsafe_imports() method checks imported modules against a hardcoded tuple:
def unsafe_imports(self) -> Iterator[ast.Import | ast.ImportFrom]:
for node in self.properties.imports:
if node.module in (
"__builtin__", "__builtins__", "builtins", "os", "posix", "nt",
"subprocess", "sys", "builtins", "socket", "pty", "marshal", "types",
):
yield node
This list is incomplete. The following dangerous modules are NOT detected:
- ctypes: Allows arbitrary memory access, calling C functions, and bypassing Python restrictions entirely
- importlib: Can dynamically import any module at runtime
- runpy: Can execute Python modules as scripts
- code: Can compile and execute arbitrary Python code
- multiprocessing: Can spawn processes with arbitrary code
Since ctypes is part of the Python standard library, it also bypasses the NonStandardImports analysis.
PoC
from fickling.fickle import Pickled
from fickling.analysis import check_safety, Severity
# Pickle that imports ctypes.pythonapi (allows arbitrary code execution)
# PROTO 4, GLOBAL 'ctypes pythonapi', STOP
payload = b'\x80\x04cctypes\npythonapi\n.'
pickled = Pickled.load(payload)
results = check_safety(pickled)
print(f"Severity: {results.severity.name}")
print(f"Is safe: {results.severity == Severity.LIKELY_SAFE}")
# Output: Severity is LIKELY_SAFE or low - the ctypes import is not flagged
# A truly malicious pickle using ctypes could execute arbitrary code
Impact
Security Bypass (Confidentiality, Integrity, Availability)
An attacker can craft a malicious pickle that:
1. Imports ctypes to gain arbitrary memory access
2. Uses ctypes.pythonapi or ctypes.CDLL to execute arbitrary code
3. Passes Fickling's safety analysis as "likely safe"
4. Executes malicious code when the victim loads the pickle after trusting Fickling's verdict
This undermines the core purpose of Fickling as a pickle safety scanner.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.1.6"
},
"package": {
"ecosystem": "PyPI",
"name": "fickling"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.1.7"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-22609"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-502"
],
"github_reviewed": true,
"github_reviewed_at": "2026-01-09T21:12:00Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "#Fickling\u0027s assessment\n\n`ctypes`, `importlib`, `runpy`, `code` and `multiprocessing` were added the list of unsafe imports (https://github.com/trailofbits/fickling/commit/9a2b3f89bd0598b528d62c10a64c1986fcb09f66, https://github.com/trailofbits/fickling/commit/eb299b453342f1931c787bcb3bc33f3a03a173f9, https://github.com/trailofbits/fickling/commit/29d5545e74b07766892c1f0461b801afccee4f91, https://github.com/trailofbits/fickling/commit/b793563e60a5e039c5837b09d7f4f6b92e6040d1, https://github.com/trailofbits/fickling/commit/b793563e60a5e039c5837b09d7f4f6b92e6040d1). \n\n# Original report\n\n## Summary\n\nThe `unsafe_imports()` method in Fickling\u0027s static analyzer fails to flag several high-risk Python modules that can be used for arbitrary code execution. Malicious pickles importing these modules will not be detected as unsafe, allowing attackers to bypass Fickling\u0027s primary static safety checks.\n\n## Details\n\nIn `fickling/fickle.py` lines 866-884, the `unsafe_imports()` method checks imported modules against a hardcoded tuple:\n\n```python\ndef unsafe_imports(self) -\u003e Iterator[ast.Import | ast.ImportFrom]:\n for node in self.properties.imports:\n if node.module in (\n \"__builtin__\", \"__builtins__\", \"builtins\", \"os\", \"posix\", \"nt\",\n \"subprocess\", \"sys\", \"builtins\", \"socket\", \"pty\", \"marshal\", \"types\",\n ):\n yield node\n```\n\nThis list is incomplete. The following dangerous modules are NOT detected:\n\n- **ctypes**: Allows arbitrary memory access, calling C functions, and bypassing Python restrictions entirely\n- **importlib**: Can dynamically import any module at runtime\n- **runpy**: Can execute Python modules as scripts\n- **code**: Can compile and execute arbitrary Python code\n- **multiprocessing**: Can spawn processes with arbitrary code\n\nSince `ctypes` is part of the Python standard library, it also bypasses the `NonStandardImports` analysis.\n\n## PoC\n\n```python\nfrom fickling.fickle import Pickled\nfrom fickling.analysis import check_safety, Severity\n\n# Pickle that imports ctypes.pythonapi (allows arbitrary code execution)\n# PROTO 4, GLOBAL \u0027ctypes pythonapi\u0027, STOP\npayload = b\u0027\\x80\\x04cctypes\\npythonapi\\n.\u0027\n\npickled = Pickled.load(payload)\nresults = check_safety(pickled)\n\nprint(f\"Severity: {results.severity.name}\")\nprint(f\"Is safe: {results.severity == Severity.LIKELY_SAFE}\")\n\n# Output: Severity is LIKELY_SAFE or low - the ctypes import is not flagged\n# A truly malicious pickle using ctypes could execute arbitrary code\n```\n\n## Impact\n\n**Security Bypass (Confidentiality, Integrity, Availability)**\n\nAn attacker can craft a malicious pickle that:\n1. Imports `ctypes` to gain arbitrary memory access\n2. Uses `ctypes.pythonapi` or `ctypes.CDLL` to execute arbitrary code\n3. Passes Fickling\u0027s safety analysis as \"likely safe\"\n4. Executes malicious code when the victim loads the pickle after trusting Fickling\u0027s verdict\n\nThis undermines the core purpose of Fickling as a pickle safety scanner.",
"id": "GHSA-q5qq-mvfm-j35x",
"modified": "2026-01-09T21:12:01Z",
"published": "2026-01-09T21:12:00Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-q5qq-mvfm-j35x"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/pull/195"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/commit/29d5545e74b07766892c1f0461b801afccee4f91"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/commit/6b400e1a2525e6a4a076c97ccc0d4d9581317101"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/commit/9a2b3f89bd0598b528d62c10a64c1986fcb09f66"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/commit/b793563e60a5e039c5837b09d7f4f6b92e6040d1"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/commit/eb299b453342f1931c787bcb3bc33f3a03a173f9"
},
{
"type": "PACKAGE",
"url": "https://github.com/trailofbits/fickling"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/blob/977b0769c13537cd96549c12bb537f05464cf09c/test/test_bypasses.py#L88"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "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",
"type": "CVSS_V4"
}
],
"summary": "Fickling has Static Analysis Bypass via Incomplete Dangerous Module Blocklist"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.