Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 41 additions & 9 deletions scripts/html_safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"""

import html
from urllib.parse import urlparse
from urllib.parse import urlparse, quote


def esc(value):
Expand Down Expand Up @@ -57,6 +57,45 @@ def safe_url(url, text=None, target=None): # pylint: disable=unused-argumen
return esc(text if text is not None else url)


def _is_report_relative(path, allow_parent=False):
"""True when ``path`` names something reachable from the report folder.

Rejects a URL scheme, a protocol-relative ``//host`` and an absolute path, so a
crafted media name cannot turn a report cell into a remote fetch. ``..`` is
rejected too unless ``allow_parent`` is set: media_to_html() genuinely emits
``../data/...`` to reach the extraction folder next to the report, and that is a
deliberate part of the report layout rather than an escape.
"""
if path.startswith(('/', '\\')):
return False
normalized = path.replace('\\', '/')
if normalized.startswith('//'):
return False
if not allow_parent and '..' in normalized.split('/'):
return False
try:
if urlparse(path).scheme:
return False
except ValueError:
return False
return True


def safe_local_path(path, allow_parent=False):
"""Percent-encode a report-relative path for use in an ``href``/``src`` attribute.

Returns ``''`` when the path is not report-relative, so a crafted media filename
can neither point the report at a remote host nor reach outside the report folder.
The encoded result is HTML-escaped as well, so it is safe inside a quoted
attribute. Use this for the attribute value; use safe_local_link() when you want
the whole anchor.
"""
path = '' if path is None else str(path).strip()
if not path or not _is_report_relative(path, allow_parent):
return ''
return esc(quote(path, safe='/.'))


def safe_local_link(path, text=None):
"""Build an ``<a href>`` to a file inside the report folder.

Expand All @@ -67,14 +106,7 @@ def safe_local_link(path, text=None):
"""
path = '' if path is None else str(path).strip()
label = esc(text if text is not None else path)
if not path:
return label
if path.startswith(('/', '\\', '//')) or '..' in path.replace('\\', '/').split('/'):
return label
try:
if urlparse(path).scheme:
return label
except ValueError:
if not path or not _is_report_relative(path):
return label
return f'<a href="{esc(path)}" target="_blank">{label}</a>'

Expand Down
30 changes: 21 additions & 9 deletions scripts/ilapfuncs.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

# LEAPP version unique imports
from typing import Pattern
from scripts.html_safe import esc, safe_local_path
from scripts.lavafuncs import lava_process_artifact, lava_insert_sqlite_data, lava_get_media_item, \
lava_insert_sqlite_media_item, lava_insert_sqlite_media_references, lava_get_media_references, \
lava_get_full_media_info
Expand Down Expand Up @@ -353,20 +354,25 @@ def relative_paths(source):
filename = Path(source).name
return f"media/{filename}"

filename = Path(media_path).name
media_path = quote(relative_paths(media_path))
# The media name comes from the evidence, so every place it is emitted is
# escaped: percent-encoded in src/href by safe_local_path(), which also refuses a
# target that would leave the report folder, and HTML-escaped in title= and in the
# fallback link text. Before this, a crafted attachment filename broke out of the
# title attribute and ran in the examiner's report (CWE-79).
filename = esc(Path(media_path).name)
media_path = safe_local_path(relative_paths(media_path))

if mimetype == None:
if mimetype is None:
mimetype = ''
if 'video' in mimetype:
thumb = f'<video width="320" height="240" controls="controls"><source src="{media_path}" type="video/mp4" preload="none">Your browser does not support the video tag.</video>'
elif 'image' in mimetype:
image_style = style if style else "max-height:300px; max-width:400px;"
thumb = f'<a href="{media_path}" target="_blank"><img title="{title}" src="{media_path}" style="{image_style}"></img></a>'
image_style = esc(style) if style else "max-height:300px; max-width:400px;"
thumb = f'<a href="{media_path}" target="_blank"><img title="{esc(title)}" src="{media_path}" style="{image_style}"></img></a>'
elif 'audio' in mimetype:
thumb = f'<audio controls><source src="{media_path}" type="audio/ogg"><source src="{media_path}" type="audio/mpeg">Your browser does not support the audio element.</audio>'
else:
thumb = f'<a href="{media_path}" target="_blank"> Link to {filename} file</>'
thumb = f'<a href="{media_path}" target="_blank"> Link to {filename} file</a>'
return thumb

def get_data_list_with_media(media_header_info, data_list):
Expand Down Expand Up @@ -913,17 +919,23 @@ def relative_paths(source, splitter):
source = relative_paths(str(source), splitter)

mimetype = guess_mime(match)
if mimetype == None:
if mimetype is None:
mimetype = ''

# allow_parent: relative_paths() above deliberately emits ../data/... to reach
# the extraction folder beside the report. The evidence filename in the
# fallback link text is escaped -- it used to be interpolated raw.
source = safe_local_path(source, allow_parent=True)
filename = esc(filename)

if 'video' in mimetype:
thumb = f'<video width="320" height="240" controls="controls"><source src="{source}" type="video/mp4" preload="none">Your browser does not support the video tag.</video>'
elif 'image' in mimetype:
thumb = f'<a href="{source}" target="_blank"><img src="{source}"width="300"></img></a>'
thumb = f'<a href="{source}" target="_blank"><img src="{source}" width="300"></img></a>'
elif 'audio' in mimetype:
thumb = f'<audio controls><source src="{source}" type="audio/ogg"><source src="{source}" type="audio/mpeg">Your browser does not support the audio element.</audio>'
else:
thumb = f'<a href="{source}" target="_blank"> Link to {filename} file</>'
thumb = f'<a href="{source}" target="_blank"> Link to {filename} file</a>'
return thumb


Expand Down
Loading