# NLTK: Allowlisted pickle loaders still permit code execution in current source

NLTK's allowlisted pickle loaders trusted whole module namespaces, letting crafted pickles invoke ReppTokenizer._execute or numpy.f2py.myeval for arbitrary code execution.

- Payout: Research
- Severity: critical
- Program: NLTK
- Found by: leduckhuong
- Disclosed: 2026-09-08
- CVE: CVE-2026-79657
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-x99w-6fgc-pmfw
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-x99w-6fgc-pmfw
- Tags: area:oss domain:oss impact:rce kind:research platform:ghsa sev:critical tech:python technique:incomplete-fix technique:source-review vuln:deserialization vuln:rce

**Target:** Python pickle deserialization in nltk.picklesec / punkt / transitionparser loaders
**Root cause:** Allowlists match module prefixes rather than exact (module, qualname) pairs, so any callable inside an allowed namespace (nltk.tokenize, numpy) is reachable via pickle REDUCE.
**Steps:**
1. Craft a pickle whose REDUCE callable is `nltk.tokenize.repp.ReppTokenizer._execute` with a command writing a marker file.
2. Pass payload to `punkt_pickle_load(BytesIO(payload))`; marker file appears during unpickling.
3. Craft a second pickle whose REDUCE callable is `numpy.f2py.crackfortran.myeval` with attacker-controlled eval string.
4. Load via `TransitionParser.parse()`; marker file is created before parse later fails on returned object type.
**How found:** Audited the allowlist implementation and noticed it trusts whole module namespaces; then enumerated dangerous callables reachable inside those namespaces (ReppTokenizer._execute, numpy.f2py.crackfortran.myeval) and confirmed both fire during unpickling.
**Exploitation:** Attacker supplies a malicious tokenizer or model artifact to an application using the public allowlisted loaders; unpickling invokes the in-namespace gadget (subprocess.Popen via ReppTokenizer._execute, or eval via numpy.f2py.crackfortran.myeval) executing arbitrary commands before type validation aborts.
**Insight:** When reviewing pickle allowlists, don't stop at 'is the module allowed' — enumerate every callable in the allowed namespace for REDUCE-reachable sinks like subprocess.Popen or eval; broad prefixes like numpy or nltk.tokenize are effectively unrestricted.

