跳到正文
DEV Community· CVE Reports·· 2 小時前AI 評分50

CVE-2026-115878:Stanford NLP Stanza 1.14.0 存在任意程式碼執行漏洞

CVE-2026-115878: CVE-2026-115878: Arbitrary Code Execution via Nested Pickle Deserialization in Stanford NLP Stanza Lemmatizer

AI 導讀

CVE-2026-115878 令 Stanford NLP Stanza 1.14.0 在載入惡意 lemmatizer 模型時可能執行任意程式碼。漏洞源於模型載入流程把 gzip 壓縮的 pickle 資料傳入不受限制的 pickle.loads(),繞過 PyTorch 的 weights_only=True 檢查。

正文

CVE Reports

CVE-2026-115878: Arbitrary Code Execution via Nested Pickle Deserialization in Stanford NLP Stanza Lemmatizer

Vulnerability ID: CVE-2026-115878
CVSS Score: 7.8
Published: 2026-11-03

CVE-2026-115878 (GHSA-gh9r-c94j-cmp5) is an arbitrary code execution vulnerability in the Stanford NLP Stanza Python library affecting version 1.14.0. The flaw originates from insecure deserialization of untrusted data (CWE-502) within the lemmatizer model loading pipeline in stanza/models/lemma/trainer.py. While Stanza utilizes PyTorch's weights_only=True flag during outer checkpoint loading, it embeds a gzip-compressed pickle byte stream inside the checkpoint dictionary. Because PyTorch treats raw bytes objects as safe primitives, the nested payload bypasses the outer allowlist and is subsequently passed to an unrestricted pickle.loads() call, enabling arbitrary code execution when loading a malicious model.

TL;DR

Stanza 1.14.0 stores lemmatizer part-of-speech dictionaries as gzip-compressed pickle byte streams inside PyTorch checkpoints. PyTorch's weights_only=True parameter permits raw bytes primitives without inspection, and Stanza subsequently passes these decompressed bytes to an unrestricted pickle.loads() call, allowing arbitrary code execution during pipeline initialization.


⚠️ Exploit Status: POC

Technical Details

  • CVE ID: CVE-2026-115878 (GHSA-gh9r-c94j-cmp5)
  • Vulnerability Class: CWE-502: Deserialization of Untrusted Data
  • CVSS v3.1 Score: 7.8 HIGH (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)
  • Affected Component: stanza/models/lemma/trainer.py (_unpack_pos_dict)
  • Affected Versions: stanza 1.14.0
  • Patched Version: stanza 1.15.0
  • Exploit Status: Public Proof-of-Concept (PoC) Available
  • CISA KEV Status: Not Listed

Affected Systems

  • Stanford NLP Stanza (PyPI: stanza)
  • stanza: >= 1.14.0, < 1.15.0 (Fixed in: 1.15.0)

Code Analysis

Commit: b21e1c9

Patch a security hole by using orjson to encode/decode the lemma dictionary instead of pickle. Remove the path that loads old models for v1.15.0.

diff --git a/setup.py b/setup.py
index 4149132462..f7ab18f691 100644
--- a/setup.py
+++ b/setup.py
@@ -80,11 +80,12 @@
     install_requires=[
         'emoji', 
+        'networkx',
         'numpy', 
+        'orjson',
         'platformdirs',
         'protobuf>=3.15.0',
         'requests', 
-        'networkx',
         'tomli;python_version<"3.11"',
         'torch>=1.13.0',
         'tqdm',
diff --git a/stanza/models/lemma/trainer.py b/stanza/models/lemma/trainer.py
index d724e6422f..a33ac1b032 100644
--- a/stanza/models/lemma/trainer.py
+++ b/stanza/models/lemma/trainer.py
@@ -30,8 +30,12 @@
 _POS_INDEPENDENT = "*"

 # Version tag stored in the checkpoint to distinguish dict formats
-_DICTS_VERSION_LEGACY = 1   # old (word_dict, composite_dict) tuple
-_DICTS_VERSION_POS    = 2   # new {pos: {word: lemma}}, gzip-pickled bytes
+_DICTS_VERSION_LEGACY     = 1   # old (word_dict, composite_dict) tuple
+_DICTS_VERSION_POS_PICKLE = 2   # new {pos: {word: lemma}}, gzip+pickle bytes (security hole, replaced by v3)
+_DICTS_VERSION_POS        = 3   # new {pos: {word: lemma}}, gzip+json bytes
+
+import orjson

 def _pack_pos_dict(pos_dict):
-    """Serialize pos_dict to gzip-compressed pickle bytes for storage."""
-    raw = pickle.dumps(pos_dict, protocol=4)
+    """Serialize pos_dict to gzip-compressed JSON bytes for storage."""
+    raw = orjson.dumps(pos_dict)
     buf = io.BytesIO()
     with gzip.GzipFile(fileobj=buf, mode="wb", compresslevel=9) as gz:
         gz.write(raw)
@@ -52,11 +56,12 @@ def _pack_pos_dict(pos_dict):

 def _unpack_pos_dict(data):
-    """Deserialize pos_dict from gzip-compressed pickle bytes."""
+    """Deserialize pos_dict from gzip-compressed JSON bytes (v3 format)."""
     buf = io.BytesIO(data)
     with gzip.GzipFile(fileobj=buf, mode="rb") as gz:
         raw = gz.read()
-    return pickle.loads(raw)
+    return orjson.loads(raw)

@@ -376,6 +381,12 @@ def load(self, filename, args, foundation_cache, lemma_classifier_args=None):
         dicts_version = checkpoint.get('dicts_version', _DICTS_VERSION_LEGACY)
         if dicts_version == _DICTS_VERSION_POS:
             self.pos_dict = _unpack_pos_dict(checkpoint['dicts'])
+        elif dicts_version == _DICTS_VERSION_POS_PICKLE:
+            raise ValueError(
+                f"Lemmatizer model {filename!r} uses a pickle-based dict format (v2) "
+                "that is no longer supported for security reasons. "
+                "Please re-download the model or run convert_lemma_dict.py to upgrade it to v3."
+            )
         else:
             # legacy format: (word_dict, composite_dict) tuple

Exploit Details

  • GitHub Security Advisory: Official advisory containing end-to-end Proof of Concept code demonstrating arbitrary command execution via a crafted lemma .pt checkpoint.

Mitigation Strategies

  • Upgrade Stanza to version 1.15.0 or later to eliminate pickle deserialization in the lemmatizer pipeline.
  • Restrict model loading exclusively to verified checkpoints downloaded from the official Stanford NLP repository.
  • Prohibit user-supplied input from controlling the lemma_model_path parameter or custom model registry paths in Stanza pipeline configurations.
  • Inspect third-party PyTorch checkpoints for embedded compressed streams under the 'dicts' key before loading them in version 1.14.0.

Remediation Steps:

  1. Update the stanza package via pip: pip install --upgrade 'stanza>=1.15.0'.
  2. Re-download official lemmatizer models so they utilize the v3 (gzip+orjson) dictionary format.
  3. For locally trained, trusted v2 lemmatizer models, run stanza/utils/lemma/convert_lemma_dict.py in an isolated environment to migrate them to the v3 format.
  4. Verify that application logs do not show ValueError exceptions indicating lingering v2 model files in production pipelines.

References


Read the full report for CVE-2026-115878 on our website for more details including interactive diagrams and full exploit analysis.

來源:DEV Community · dev.to