Skip to content

Commit 0e6b8cc

Browse files
g0blinResearchg0blin
andauthored
Merge commit from fork
* Prevent path traversal when embedding images * Switch from realpath to abspath to avoid symlink abuse --------- Co-authored-by: g0blin <g0blin@hackthebox.com>
1 parent ba5e5cd commit 0e6b8cc

2 files changed

Lines changed: 33 additions & 0 deletions

File tree

nbconvert/filters/markdown_mistune.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,11 @@ def _src_to_base64(self, src: str) -> Optional[str]:
437437
"""
438438
src_path = os.path.join(self.path, src)
439439

440+
resolved = os.path.abspath(src_path)
441+
allowed_base = os.path.abspath(self.path)
442+
if not resolved.startswith(allowed_base + os.sep) and resolved != allowed_base:
443+
return None
444+
440445
if not os.path.exists(src_path):
441446
return None
442447

tests/exporters/test_html.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
# Copyright (c) IPython Development Team.
44
# Distributed under the terms of the Modified BSD License.
55

6+
import base64
7+
import os
68
import re
9+
from tempfile import TemporaryDirectory
710

811
import pytest
912
from nbformat import v4
@@ -264,6 +267,31 @@ def test_language_code_error(self):
264267

265268
assert '<html lang="en">' in output
266269

270+
def test_embed_images_path_traversal_blocked(self):
271+
"""Path traversal in image src should be blocked when embed_images=True"""
272+
with TemporaryDirectory() as parent_dir:
273+
# Create a secret file that an attacker would try to exfiltrate
274+
secret_content = b"SUPERSECRETCONTENT"
275+
with open(os.path.join(parent_dir, "secret.txt"), "wb") as f:
276+
f.write(secret_content)
277+
278+
# Create a temp subdirectory to serve as the notebook's working path
279+
with TemporaryDirectory(dir=parent_dir) as notebook_dir:
280+
# Build a notebook with path traversal image references
281+
nb = v4.new_notebook()
282+
nb.cells.append(v4.new_markdown_cell("![exfil](../secret.txt)"))
283+
nb.cells.append(v4.new_markdown_cell('<img src="../secret.txt" alt="exfil"/>'))
284+
285+
exporter = HTMLExporter()
286+
exporter.embed_images = True
287+
html, _ = exporter.from_notebook_node(
288+
nb, resources={"metadata": {"path": notebook_dir}}
289+
)
290+
291+
# The secret content must NOT appear as base64 in the output
292+
target_b64 = base64.b64encode(secret_content).decode()
293+
self.assertNotIn(target_b64, html)
294+
267295

268296
@pytest.mark.parametrize(
269297
("lexer_options"),

0 commit comments

Comments
 (0)