🛡️ CVE-2026-54071 — babeldoc

🟠 CVSS 8.0 — High ✅ No Known Exploit CWE-502 OSV
8.0
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

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

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:

```python

@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):

```diff

--- 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

How this vulnerability can be exploited

This issue can be reached with local access to the system, attack complexity is low, an attacker needs no privileges on the target. A user must be tricked into taking some action. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality high, integrity high, availability high.

Weakness class

CVE-2026-54071 is classified as CWE-502: Deserialization of Untrusted Data. Serialised data from an untrusted source is reconstructed into objects, which can trigger code during the process.

Affected software

CVE-2026-54071 is recorded against 1 package.

  • babeldoc (fixed in 0.6.3)

Timeline and source

Published on 10 July 2026 and last revised on 13 July 2026. No public exploit is currently recorded for this entry. Record sourced from OSV.

References

github.com (Web)
github.com (Package)

Details

Severity HIGH
CVSS Score 8.0
CVSS Vector CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE CWE-502
Public Exploit ✅ No
Source OSV
Published 2026-07-10
Updated 2026-08-12
Modified 2026-07-13
Fix URL N/A

Affected Packages

Software From version Fixed in
babeldoc 0.6.3

Site Security Check

Is babeldoc part of your stack?

CVE-2026-54071 is rated CVSS 8.0 High. BotEraser scans your installation against known CVE records and tells you whether this vulnerability applies to the versions you actually run.

Scan My Site Free →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.