# Deserialization denylist bypass to code execution

Attacker bytes reach unserialize/pickle.loads guarded only by name denylists or weak path checks

_Area: Web injection and code execution. Seen in 5 disclosed reports; latest 2026-08-04. Updated 2026-10-25._

## How it works
Deserialization turns attacker bytes into live objects, and object hooks (`__reduce__`, POP chains) run code during that conversion. A safe guard is structural: an allowlist of permitted types, or a path that provably stays inside a trusted directory. The pattern here is a guard that filters instead. A denylist of dangerous names assumes it enumerates every sink; a path check assumes the attacker's segment cannot escape. When either assumption breaks, attacker data reaches `unserialize`/`pickle.loads` and code runs.

## Where it shows up
- `unserialize()`/`pickle.loads()` on a request field, or on a file whose name comes from parsed input [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-m8gf-v64p-gfmg.md)
- A regex or keyword denylist as the only guard around a code-execution sink [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-x6vm-w76m-8j7g.md)
- A sandbox that pre-imports powerful libraries whose own loaders (`read_pickle`) are not on the list [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-x6vm-w76m-8j7g.md)
- A filename joined into a trusted directory with only NUL stripping [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-m8gf-v64p-gfmg.md)
- `os.path.join()` with an attacker segment that can be absolute, silently dropping the base dir [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-m8gf-v64p-gfmg.md)
- PDF name hex escapes (`#xx`) decoding to `/` before the join [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-m8gf-v64p-gfmg.md)
- Scanners that inspect only import opcodes, not `REDUCE` arguments [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-vvpj-8cmc-gx39.md)
- Scanner blocklists missing stdlib modules with shell helpers [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-g38g-8gr9-h9xp.md), or keyed on `Class.method` compared against bare global names [E5](https://bugbountylibrary.com/r/ghsa%3AGHSA-7wx9-6375-f5wh.md)

## How to review
Trace every input reaching a deserializer and ask what constrains it. The invariant: no attacker-influenced bytes may select the object type or the file path, and the guard must be an allowlist, not a denylist. For paths, confirm the resolved path stays under the intended directory after decoding and normalization, and reject absolute segments [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-m8gf-v64p-gfmg.md). For sandboxes, enumerate the dangerous APIs of every pre-imported library, not just blocked keywords [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-x6vm-w76m-8j7g.md). For scanners, check that `REDUCE` arguments and stdlib indirection gadgets are analyzed [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-vvpj-8cmc-gx39.md), that stdlib modules with shell helpers are covered [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-g38g-8gr9-h9xp.md), and that blocklist keys match how globals are actually named [E5](https://bugbountylibrary.com/r/ghsa%3AGHSA-7wx9-6375-f5wh.md).

## Variants seen
- Python regex denylist over interpolated code bypassed through `pandas.read_pickle` [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-x6vm-w76m-8j7g.md)
- PDF-controlled CMap name with hex-encoded absolute path redirecting `pickle.loads` [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-m8gf-v64p-gfmg.md)
- Scanner blocklist bypassed via `pkgutil.resolve_name` [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-vvpj-8cmc-gx39.md); via omitted stdlib modules [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-g38g-8gr9-h9xp.md); via `Profile.run` vs `profile.run` mismatch [E5](https://bugbountylibrary.com/r/ghsa%3AGHSA-7wx9-6375-f5wh.md)

## Why fixes fail
Denylists miss library-internal sinks [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-x6vm-w76m-8j7g.md) and stdlib modules [E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-g38g-8gr9-h9xp.md); NUL-stripping leaves traversal and absolute-path joins intact [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-m8gf-v64p-gfmg.md); exact-string blocklist keys miss module-level equivalents [E5](https://bugbountylibrary.com/r/ghsa%3AGHSA-7wx9-6375-f5wh.md).

## Impact and severity
Remote code execution in [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-x6vm-w76m-8j7g.md)[E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-m8gf-v64p-gfmg.md); high severity. Worse when the sink is reachable through ordinary request or file-parse paths [E1](https://bugbountylibrary.com/r/ghsa%3AGHSA-x6vm-w76m-8j7g.md)[E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-m8gf-v64p-gfmg.md). Scanner bypasses [E3](https://bugbountylibrary.com/r/ghsa%3AGHSA-vvpj-8cmc-gx39.md)[E4](https://bugbountylibrary.com/r/ghsa%3AGHSA-g38g-8gr9-h9xp.md)[E5](https://bugbountylibrary.com/r/ghsa%3AGHSA-7wx9-6375-f5wh.md) let malicious pickles pass as clean.

## Evidence
- E1: https://bugbountylibrary.com/r/ghsa%3AGHSA-x6vm-w76m-8j7g.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-m8gf-v64p-gfmg.md
- E3: https://bugbountylibrary.com/r/ghsa%3AGHSA-vvpj-8cmc-gx39.md
- E4: https://bugbountylibrary.com/r/ghsa%3AGHSA-g38g-8gr9-h9xp.md
- E5: https://bugbountylibrary.com/r/ghsa%3AGHSA-7wx9-6375-f5wh.md
