CVE-2026-79657: NLTK before 3.10.3 Remote Code Execution via Unsafe Pickle Deserialization

Published Aug 25, 2026
·
Updated

Summary

The current source tree still allows arbitrary code execution during supposedly safer allowlisted pickle loading. The allowlist trusts whole module namespaces instead of exact safe globals, so crafted pickles can invoke dangerous in-namespace callables through pickle REDUCE.

Details

- Vulnerability type: Remote code execution via unsafe deserialization - Affected component: nltk.picklesec.allowlistedpickleload, nltk.tokenize.punkt.punktpickleload, nltk.parse.transitionparser.TransitionParser.parse - Affected versions: Current source v3.10.0-rc2; published 3.9.4 was not the claim target for this bypass. - Patched versions: Not yet patched - Root cause: Module-prefix allowlists include dangerous callables such as nltk.tokenize.repp.ReppTokenizer.execute and numpy.f2py.crackfortran.myeval.

punktpickleload() allowlists both nltk.tokenize.punkt and the whole nltk.tokenize namespace, which exposes ReppTokenizer.execute() and its subprocess.Popen(...) sink during unpickling. TransitionParser.parse() uses allowlistedpickleload(..., allowedmodules=("numpy", "scipy", "sklearn")), which permits numpy.f2py.crackfortran.myeval() and its attacker-controlled eval(...) path. I confirmed both gadgets create marker files before the caller returns or later aborts on type misuse.

PoC

Preconditions - The application loads an attacker-controlled tokenizer or model artifact through these public loaders.

Steps 1. Create a pickle whose REDUCE callable is ReppTokenizer.execute and point its command to a harmless marker-file write. 2. Pass that payload to punktpickleload(BytesIO(payload)) and observe the marker file is created during unpickling. 3. Create a second pickle whose REDUCE callable is numpy.f2py.crackfortran.myeval and load it through TransitionParser.parse(). 4. Observe the second marker file is created before TransitionParser.parse() later fails on the returned object type.

Minimal reproducible excerpt

text {'punktmarker': 'PUNKTRCE', 'transitionparsermarker': 'TPRCE'}

Impact

Any caller that trusts these current allowlisted loaders can still execute attacker-controlled commands while loading model or tokenizer artifacts. This defeats the protection mechanism that replaced unrestricted pickle loading and creates a dangerous false sense of safety.

Remediation

Replace broad module-prefix allowlists with exact (module, qualname) pairs for the few safe classes or functions genuinely required. Do not allow entire namespaces such as nltk.tokenize or numpy, and keep post-load type validation only as a secondary defense.

Resources

- https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/tokenize/punkt.py#L120-L134 - https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/tokenize/repp.py#L111-L115 - https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/parse/transitionparser.py#L26-L30 - https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/parse/transitionparser.py#L565-L571

---

Fix + attack demonstration (verified)

+ tightened callers findclass now, before the allowlists: 1. Rejects any dotted name → closes 4489 with zero legit impact. 2. Denies dangerous modules (os, subprocess, sys, builtins, numpy.f2py, nltk.tokenize.repp, …) even under a broad allowedmodules — a defense-in-depth backstop so a future too-broad allowlist can't silently reopen RCE. 3. builtins denied wholesale; safe primitives (int, str, …) must be named exactly via allowedglobals.

Callers tightened: punkt drops the broad nltk.tokenize (keeps nltk.tokenize.punkt + exact collections.defaultdict/builtins.int); transitionparser keeps numpy/scipy/sklearn (array unpickling needs their submodules) with the new guards blocking the gadgets.

Full pickle-sink audit Every deserialization sink in the tree was reviewed: no raw pickle.load anywhere, and no joblib/numpy/torch/dill/yaml/marshal loaders. data.load + wordnetapp use RestrictedUnpickler (blocks all globals — safe); the remaining pickleload sites (chartparserapp, tbl/demo) load user-selected or self-written files and keep their warning.

Attack demonstration (captured; fork clone) === EXPLOITS blocked === 4489 sklearn.os.system (dotted) -> BLOCKED x99w numpy.f2py.crackfortran.myeval -> BLOCKED x99w nltk.tokenize.repp.execute -> BLOCKED backstop os.system (os allowlisted) -> BLOCKED backstop builtins.eval (exact global)-> BLOCKED === LEGIT loads still work === punkt round-trip via punktpickleload -> OK builtins.int (safe primitive) -> OK

Tests testpickleallowlistsecurity.py — added 5 regressions (dotted traversal, both namespace gadgets, denied-module backstop, legit round-trip). Suite: 122 passed / 9 skipped (sklearn-dependent) across pickle/punkt/transition/tokenize. pre-commit (black/isort/ruff) clean.

Other sources

NLTK versions before 3.10.3 contain a remote code execution vulnerability in allowlisted pickle loaders that trust entire module namespaces instead of specific safe callables. Attackers can craft malicious pickle payloads invoking dangerous in-namespace functions like ReppTokenizer.execute and numpy.f2py.crackfortran.myeval through pickle REDUCE to execute arbitrary commands during model or tokenizer artifact loading.

— MITRE

Affected Software

3 affected componentsFixes available
NLTK Project NLTK<3.10.3
nltk nltk<3.10.3
pip/nltk<=3.10.2
3.10.3

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/nltk to a version that resolves this vulnerability.

    Fixed in 3.10.3
  2. Upgrade

    Upgrade nltk to a version that resolves this vulnerability.

    Fixed in 3.10.3
  3. Configuration

    Modify TransitionParser.parse to stop allowing whole module namespaces via allowlisted_pickle_load(..., allowed_modules=("numpy", "scipy", "sklearn")). Use exact (module, qualname) allowlisting for only the specific safe globals needed, to prevent gadgets like numpy.f2py.crackfortran.myeval (and its attacker-controlled eval path) from being reachable during unpickling.

    nltk.parse.transitionparser.TransitionParser.parse allowlisted_pickle_load allowed_modules = Exact (module, qualname) pairs instead of module-prefix namespaces such as ("numpy", "scipy", "sklearn")
  4. Configuration

    Tighten punkt_pickle_load allowlisting: drop broad module-prefix allowlist for "nltk.tokenize" and allow only "nltk.tokenize.punkt" plus explicitly named safe globals (e.g., collections.defaultdict and builtins.int). Ensure builtins are not allowed wholesale; require exact entries via allowed_globals to prevent in-namespace gadgets such as nltk.tokenize.repp.ReppTokenizer._execute from executing during unpickling.

    nltk.tokenize.punkt.punkt_pickle_load allowlisted_pickle_load allowed_modules/allowlisted namespaces = Do not allow "nltk.tokenize" namespace; keep only "nltk.tokenize.punkt" plus exact allowed globals (e.g., collections.defaultdict and builtins.int)
  5. Compensating control

    Enforce a hard backstop denylist during allowlisted pickle loading: deny dangerous modules/globals such as os, subprocess, sys, builtins (and other explicitly listed dangerous entries like numpy.f2py and nltk.tokenize.repp), even if an overly-broad allowed_modules is configured in the future.

Event History

Aug 25, 2026
CVE Published
via MITRE·11:33 AM
Data Sourced
via MITRE·11:33 AM
DescriptionSeverityWeakness
Data Sourced
via NVD·12:16 PM
DescriptionSeverityWeaknessAffected Software
Sep 8, 2026
Advisory Published
via GitHub·04:42 PM
Data Sourced
via GitHub·04:42 PM
DescriptionWeaknessAffected Software

Frequently Asked Questions

1

Which deployments are exposed?

Deployments using NLTK versions earlier than 3.10.3 are exposed when they load model or tokenizer artifacts through the affected allowlisted pickle loaders. The risk is especially relevant where an attacker can cause a crafted artifact to be loaded.

2

What does an attacker need to exploit this issue?

An attacker needs to provide or make the target load a malicious pickle payload. The vulnerability has a network attack vector and requires no privileges or user interaction according to the supplied severity vector.

3

What is the immediate remediation?

Upgrade NLTK to version 3.10.3 or later. Until upgrading is possible, avoid loading untrusted model or tokenizer artifacts through the affected pickle-loading paths.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203