GHSA-6ww7-3frv-cqxh: SSRF

Published Sep 8, 2026
·
Updated

Summary

Current NLTK source reopens SSRF in proxied environments. pathsec.urlopen() validates the requested hostname locally, but once proxy inheritance is enabled the real fetch is performed by the proxy rather than by the validated direct-connect socket path.

Details

- Vulnerability type: Server-side request forgery - Affected component: nltk.pathsec.urlopen, nltk.data.load, nltk.downloader.Downloader.index, nltk.downloader.Downloader.download - Affected versions: Current source v3.10.0-rc2; published 3.9.4 was a negative control and did not reproduce. - Patched versions: Not yet patched - Root cause: Proxy-handler inheritance disables SafeHTTPHandler and SafeHTTPSHandler, so the validated hostname no longer matches the actual egress destination.

The hardened direct path pins the validated numeric destination IP before opening the socket. The proxied branch instead copies ProxyHandler instances from the global opener, marks the request as proxied, and skips the pinned handlers. I confirmed that a validated public URL can be fetched from a loopback-only internal service through the proxy path via pathsec.urlopen(), nltk.data.load(), Downloader.index(), and Downloader.download().

PoC

Preconditions - The runtime has an HTTP proxy configured and the caller relies on pathsec to keep network fetches SSRF-safe.

Steps 1. Start a loopback-only HTTP server that serves secret text, a valid downloader index, and a ZIP payload. 2. Configure a proxy that forwards a validated public URL to that internal loopback service. 3. Call pathsec.urlopen() or nltk.data.load() on the public URL and observe the internal response is returned. 4. Instantiate Downloader(serverindexurl=...), call index() and download(), and observe internal-only content is parsed and installed.

Minimal reproducible excerpt

text {'urlopen': 'PROXYTEXTSECRET', 'dataload': 'PROXYTEXTSECRET', 'downloadedfile': 'INTERNALZIPSECRET'}

Impact

Consumers that trust pathsec as an SSRF barrier in proxied environments can be made to read internal-only HTTP resources, load forged downloader indexes, and install attacker-chosen package content fetched from the proxy's network view.

Remediation

Preserve destination validation for the actual proxy egress target or fail closed when the request would otherwise downgrade into an unpinned proxied path. Add regression tests across pathsec.urlopen, nltk.data.load, and downloader fetches with a configured proxy.

References

- https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/pathsec.py#L468-L518 - https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/data.py#L1247-L1283 - https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/downloader.py#L875-L889 - https://github.com/nltk/nltk/blob/v3.10.0-rc2/nltk/downloader.py#L1220-L1226 - https://github.com/nltk/nltk/blob/3.9.4/nltk/pathsec.py#L245-L250

---

Fix + attack demonstration (verified)

NLTK cannot pin the egress through a proxy, so it stops pretending to: under ENFORCE a proxied fetch is refused rather than performed unvalidated. Operators who trust their proxy opt back in with NLTKALLOWPROXIEDURLOPEN=1 or nltk.pathsec.ALLOWPROXIEDFETCH=True; under ENFORCE=False the refusal degrades to a warning. This closes the whole class (environment proxies and explicit ProxyHandler alike), because NLTK declines any fetch whose egress it cannot validate.

Attack demonstration (reproduced; captured output) A loopback HTTP server stands in for the internal target; httpproxy points at it; NLTK is asked for a public IP URL.

Before the fix — the internal secret is exfiltrated through the proxy: validatenetworkurl(public): PASSED BYPASS: pathsec.urlopen returned INTERNAL content via proxy: 'INTERNALONLYSECRET'

After the fix — five scenarios, isolated subprocesses: | Scenario | Result | |---|---| | proxied (env) + ENFORCE | PermissionError — blocked | | proxied + opt-in | returns secret — escape hatch works | | explicit ProxyHandler (not env) + ENFORCE | PermissionError — blocked (whole class) | | no proxy (direct) | internal IP still refused — pinning intact | | proxied + ENFORCE=False | returns secret + warns |

Tests nltk/test/unit/testpathsec.py: 64 passed. Added an end-to-end regression (testproxiedfetchdoesnotreachinternaltarget) plus testenvproxyfailsclosedunderenforce; the prior testenvproxyskipspinninghandlers (which encoded the vulnerable path) is re-expressed as the opt-in case. Existing direct-path DNS-rebinding and IP-policy tests unchanged and passing. pre-commit (isort/black/ruff) clean.

Note The upfront validatenetworkurl() and the direct-path IP pinning (from the earlier DNS-rebinding fixes, CVE-2026-54296 / GHSA-qvv7) are unchanged — this only closes the proxied downgrade they didn't cover.

Affected Software

1 affected componentFixes available
pip/nltk<=3.10.2
3.10.3

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/nltk to a version that resolves this vulnerability.

    Fixed in 3.10.3
  2. Upgrade

    Upgrade nltk to a version that resolves this vulnerability.

    Fixed in 3.9.4
  3. Configuration

    Under proxied environments, keep proxied fetches refused by ensuring nltk.pathsec.ALLOW_PROXIED_FETCH is not enabled; the ENFORCE behavior blocks proxied downgrade attempts.

    nltk.pathsec ALLOW_PROXIED_FETCH = False
  4. Configuration

    If you must allow proxied URL opens (escape hatch), explicitly set NLTK_ALLOW_PROXIED_URLOPEN=1; otherwise leave it unset so proxied fetches fail/are refused under ENFORCE.

    nltk.pathsec NLTK_ALLOW_PROXIED_URLOPEN = 1
  5. Configuration

    Set ENFORCE=True so that when a fetch would otherwise downgrade into an unpinned proxied path, NLTK refuses the request rather than performing it.

    nltk.pathsec ENFORCE = True
  6. Configuration

    Avoid running with ENFORCE=False in proxied environments, since refusal degrades to a warning and proxied fetches may return internal-only content.

    nltk.pathsec ENFORCE = False

Event History

Sep 8, 2026
Advisory Published
via GitHub·04:41 PM
Data Sourced
via GitHub·04:41 PM
DescriptionWeaknessAffected Software

Frequently Asked Questions

1

Which deployments are exposed?

Deployments using the current NLTK source v3.10.0-rc2 are affected when proxy-handler inheritance is enabled. The published 3.9.4 release was tested as a negative control and did not reproduce the issue.

2

What does an attacker need to exploit this?

An attacker needs a path that causes NLTK to fetch a URL through an inherited proxy. The proxy must be able to route a hostname that passes local validation to a loopback-only internal service or other unintended destination.

3

Are standard direct requests affected?

The issue is specific to the proxied branch. The direct path pins the validated numeric destination IP before opening the socket, while inherited proxy handling bypasses those pinned safe handlers.

4

Which NLTK functions should be reviewed for exposure?

Review uses of nltk.pathsec.urlopen, nltk.data.load, nltk.downloader.Downloader.index, and nltk.downloader.Downloader.download. These components are identified as affected by the proxy-handling behavior.

5

Is a patched version available?

No patched version is identified in the advisory data. Until one is available, prioritize preventing affected NLTK requests from using inherited proxy handlers where feasible.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203