diff --git a/0001-add-patch-to-fix-CVE-2026-33230.patch b/0001-add-patch-to-fix-CVE-2026-33230.patch new file mode 100644 index 0000000000000000000000000000000000000000..6b22e4c60ddaf86ff770eec18ef25b913ab8e9f0 --- /dev/null +++ b/0001-add-patch-to-fix-CVE-2026-33230.patch @@ -0,0 +1,33 @@ +From 1c3f799607eeb088cab2491dcf806ae83c29ad8f Mon Sep 17 00:00:00 2001 +From: natgillin +Date: Wed, 18 Mar 2026 07:28:04 -0400 +Subject: [PATCH] Fix XSS in wordnet_app lookup route + +Escape `word` with html.escape() in the "not found" message on +line 796. The search route (line 136) already escapes user input +but the lookup_ route did not, allowing reflected XSS via crafted +lookup_ URLs. + +Co-Authored-By: Claude Opus 4.6 (1M context) +--- + nltk/app/wordnet_app.py | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/nltk/app/wordnet_app.py b/nltk/app/wordnet_app.py +index 0f499eb..c8a8e7c 100644 +--- a/nltk/app/wordnet_app.py ++++ b/nltk/app/wordnet_app.py +@@ -799,7 +799,9 @@ def page_from_reference(href): + except KeyError: + pass + if not body: +- body = "The word or words '%s' were not found in the dictionary." % word ++ body = "The word or words '%s' were not found in the dictionary." % html.escape( ++ word ++ ) + return body, word + + +-- +2.43.7 + diff --git a/0002-add-patch-to-fix-CVE-2026-33231.patch b/0002-add-patch-to-fix-CVE-2026-33231.patch new file mode 100644 index 0000000000000000000000000000000000000000..3125b95a9644f29733795e2c2a58e9e60dd4d38f --- /dev/null +++ b/0002-add-patch-to-fix-CVE-2026-33231.patch @@ -0,0 +1,39 @@ +From 1b6a569d7bab2c697bc1fc245f55ac0102079c18 Mon Sep 17 00:00:00 2001 +From: natgillin +Date: Wed, 18 Mar 2026 07:36:44 -0400 +Subject: [PATCH] Bind WordNet browser to localhost only + +Change HTTPServer bind address from "" (all interfaces) to +"127.0.0.1" (localhost only). This prevents remote clients from +accessing the server, mitigating the unauthenticated shutdown +vulnerability where any reachable client could terminate the +process via GET /SHUTDOWN%20THE%20SERVER. + +The WordNet browser is a local development tool and should not +be accessible from the network. + +Co-Authored-By: Claude Opus 4.6 (1M context) +--- +--- + nltk/app/wordnet_app.py | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/nltk/app/wordnet_app.py b/nltk/app/wordnet_app.py +index c8a8e7c..2875998 100644 +--- a/nltk/app/wordnet_app.py ++++ b/nltk/app/wordnet_app.py +@@ -234,8 +234,9 @@ def wnb(port=8000, runBrowser=True, logfilename=None): + server_ready = threading.Event() + browser_thread = startBrowser(url, server_ready) + +- # Start the server. +- server = HTTPServer(("", port), MyServerHandler) ++ # Start the server. Bind to localhost only to prevent remote access ++ # and unauthenticated shutdown via /SHUTDOWN%20THE%20SERVER. ++ server = HTTPServer(("127.0.0.1", port), MyServerHandler) + if logfile: + logfile.write("NLTK Wordnet browser server running serving: %s\n" % url) + if runBrowser: +-- +2.43.7 + diff --git a/0003-add-patch-to-fix-CVE-2026-33236.patch b/0003-add-patch-to-fix-CVE-2026-33236.patch new file mode 100644 index 0000000000000000000000000000000000000000..561af7edf04fcbc2f94b5934752a39b2be4beb39 --- /dev/null +++ b/0003-add-patch-to-fix-CVE-2026-33236.patch @@ -0,0 +1,76 @@ +From 75917efc66ab122bf4b7ea9ffc33e8f8b39c5dce Mon Sep 17 00:00:00 2001 +From: natgillin +Date: Wed, 18 Mar 2026 08:09:57 -0400 +Subject: [PATCH] Fix path traversal in downloader via malicious XML index + +Validate subdir and id in Package.__init__() to reject absolute paths +and parent directory references from XML index attributes. Add +defense-in-depth check in _download_package() to verify the resolved +filepath stays within download_dir before writing. + +The attack allowed a malicious XML index server to provide crafted +subdir values (e.g., "../../etc" or "/usr/lib/python3/site-packages") +that caused os.path.join() to write files outside the download +directory. + +Two layers of defense: +1. Package.__init__(): reject absolute paths and ".." in subdir/id + at parse time, before any filesystem operations +2. _download_package(): verify resolved filepath is within + download_dir using realpath, consistent with existing zip-slip + protection in _unzip_iter() + +Co-Authored-By: Claude Opus 4.6 (1M context) +--- + nltk/downloader.py | 24 ++++++++++++++++++++++++ + 1 file changed, 24 insertions(+) + +diff --git a/nltk/downloader.py b/nltk/downloader.py +index 27c86d1..91cbbdf 100644 +--- a/nltk/downloader.py ++++ b/nltk/downloader.py +@@ -216,10 +216,22 @@ class Package: + self.name = name or id + """A string name for this package.""" + ++ # Validate subdir to prevent path traversal from malicious XML index ++ if os.path.isabs(subdir) or ".." in subdir.replace("\\", "/").split("/"): ++ raise ValueError( ++ f"Invalid package subdir {subdir!r}: must be a relative path " ++ f"without parent directory references" ++ ) + self.subdir = subdir + """The subdirectory where this package should be installed. + E.g., ``'corpora'`` or ``'taggers'``.""" + ++ # Validate id to prevent path traversal ++ if os.sep in id or "/" in id or "\\" in id or ".." in id: ++ raise ValueError( ++ f"Invalid package id {id!r}: must not contain path separators" ++ ) ++ + self.url = url + """A URL that can be used to download this package's file.""" + +@@ -677,6 +689,18 @@ class Downloader: + + # Check for (and remove) any old/stale version. + filepath = os.path.join(download_dir, info.filename) ++ ++ # Defense-in-depth: verify filepath stays within download_dir ++ real_download = os.path.realpath(os.path.abspath(download_dir)) ++ real_filepath = os.path.realpath(os.path.abspath(filepath)) ++ if not real_filepath.startswith(real_download + os.sep): ++ yield ErrorMessage( ++ info, ++ f"Path traversal blocked: package '{info.id}' attempted to " ++ f"write outside download directory (subdir='{info.subdir}')", ++ ) ++ return ++ + if os.path.exists(filepath): + if status == self.STALE: + yield StaleMessage(info) +-- +2.43.7 + diff --git a/python-nltk.spec b/python-nltk.spec index 14b7e386b710d75e1c7ea2df46c15772487079e2..badbf49a810dc8148cf02e6c11fe7b9793d4166c 100644 --- a/python-nltk.spec +++ b/python-nltk.spec @@ -1,4 +1,4 @@ -%define anolis_release 1 +%define anolis_release 2 %global pypi_name nltk %global pypi_version 3.9.3 @@ -11,6 +11,12 @@ License: Apache License, Version 2.0 URL: https://www.nltk.org/ Source0: https://files.pythonhosted.org/packages/source/n/nltk/nltk-%{version}.tar.gz BuildArch: noarch +# https://github.com/nltk/nltk/commit/1c3f799607eeb088cab2491dcf806ae83c29ad8f +Patch1: 0001-add-patch-to-fix-CVE-2026-33230.patch +# https://github.com/nltk/nltk/commit/1b6a569d7bab2c697bc1fc245f55ac0102079c18 +Patch2: 0002-add-patch-to-fix-CVE-2026-33231.patch +# https://github.com/nltk/nltk/commit/75917efc66ab122bf4b7ea9ffc33e8f8b39c5dce +Patch3: 0003-add-patch-to-fix-CVE-2026-33236.patch BuildRequires: python3-devel BuildRequires: python3dist(click) @@ -50,7 +56,7 @@ processing. %prep -%autosetup -n %{pypi_name}-%{pypi_version} +%autosetup -n %{pypi_name}-%{pypi_version} -p1 # Remove bundled egg-info rm -rf %{pypi_name}.egg-info %generate_buildrequires @@ -71,6 +77,9 @@ rm -rf %{pypi_name}.egg-info %{_bindir}/nltk %changelog +* Tue Apr 07 2026 lzq11122 - 3.9.3-2 +- Add patch to fix CVE-2026-33230,CVE-2026-33231,CVE-2026-33236 + * Wed Mar 11 2026 lzq11122 - 3.9.3-1 - Update to 3.9.3 to fix CVE-2025-14009