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

NLTK pathsec SSRF filter validates a hostname's first DNS resolution but urllib re-resolves at connect time, so a TTL-0 rebinding record reaches loopback/internal services even under ENFORCE.

- Payout: Research
- Severity: high
- Program: NLTK
- Found by: LinZiyuu
- Disclosed: 2026-07-31
- CVE: CVE-2026-12075
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-qvv7-cg9c-w4x3
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-qvv7-cg9c-w4x3
- Tags: area:oss domain:oss impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:python technique:source-review vuln:access-control vuln:ssrf

**Target:** nltk.pathsec.urlopen (nltk.download / nltk.data.load) HTTP fetch layer
**Root cause:** validate_network_url() resolves the hostname and checks the IP, but urlopen() passes the raw hostname to urllib, which independently re-resolves via getaddrinfo at connect time; the validated IP is never the connected IP. The lru_cache on _resolve_hostname only memoizes the validation lookup and does not affect the connection-layer resolution.
**Steps:**
1. Set `ps.ENFORCE = True` to enable the documented strict SSRF sandbox.
2. Control: fetch `http://127.0.0.1:<port>/` directly; pathsec raises PermissionError, confirming the filter is active.
3. Attacker controls authoritative DNS for `rebind.attacker.test` with a TTL-0 record returning a public IP on first lookup and 127.0.0.1 on subsequent lookups.
4. Fetch `http://rebind.attacker.test:<port>/` via `ps.urlopen`; validation lookup #0 returns `93.184.216.34` (passes), connection lookup #1 returns `127.0.0.1`.
5. Response body from the loopback-only service is returned to the caller, exfiltrating the secret despite ENFORCE.
**How found:** Noticed that validate_network_url() and urllib's connection layer are independent resolution paths with independent caches; the lru_cache docstring claiming rebinding mitigation only memoizes the validation-side lookup, so the connect-time getaddrinfo is unprotected.
**Exploitation:** Attacker supplies a URL (e.g. via nltk.download or nltk.data.load with format=raw) pointing at an attacker-controlled hostname whose DNS answers flip from a public IP to an internal/loopback IP between the validation and connection resolutions. Because the fetched body is returned to the caller, this is non-blind SSRF: internal admin UIs and cloud metadata (169.254.169.254) responses, including IAM credentials, can be read out.
**Escalation:** Non-blind read of cloud instance metadata service yields IAM/service credentials, pivoting to cloud account compromise.
**Insight:** When auditing SSRF filters, check whether the validated IP is the one actually connected to: any design that resolves-then-passes-hostname to a client library (urllib, requests, curl) is rebindable. lru_cache on the validator is a red flag, not a mitigation — it caches only the check, not the connect.

