CVE-2025-52559: Generate HTML for digest message sender safely.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg
2025-06-24 15:51:09 -07:00
committed by Tim Abbott
parent 6608c87772
commit 1a8429e338
2 changed files with 11 additions and 7 deletions

View File

@@ -508,7 +508,7 @@ ignore = [
] ]
[tool.ruff.lint.flake8-bandit] [tool.ruff.lint.flake8-bandit]
allowed-markup-calls = ["lxml.html.tostring"] allowed-markup-calls = ["bs4.BeautifulSoup.decode", "lxml.html.tostring"]
[tool.ruff.lint.flake8-gettext] [tool.ruff.lint.flake8-gettext]
extend-function-names = ["gettext_lazy"] extend-function-names = ["gettext_lazy"]

View File

@@ -219,11 +219,13 @@ def build_message_list(
return re.sub(r"\[(\S*)\]\((\S*)\)", r"\2", content) return re.sub(r"\[(\S*)\]\((\S*)\)", r"\2", content)
def prepend_sender_to_message( def prepend_sender_to_message(
message_plain: str, message_html: str, sender: str message_plain: str, message_html: Markup, sender: str
) -> tuple[str, str]: ) -> tuple[str, Markup]:
message_plain = f"{sender}:\n{message_plain}" message_plain = f"{sender}:\n{message_plain}"
message_soup = BeautifulSoup(message_html, "html.parser") message_soup = BeautifulSoup(message_html, "html.parser")
sender_name_soup = BeautifulSoup(f"<b>{sender}</b>: ", "html.parser") sender_name_soup = BeautifulSoup(
Markup("<b>{sender}</b>: ").format(sender=sender), "html.parser"
)
first_tag = message_soup.find() first_tag = message_soup.find()
if first_tag and first_tag.name == "div": if first_tag and first_tag.name == "div":
first_tag = first_tag.find() first_tag = first_tag.find()
@@ -231,9 +233,11 @@ def build_message_list(
first_tag.insert(0, sender_name_soup) first_tag.insert(0, sender_name_soup)
else: else:
message_soup.insert(0, sender_name_soup) message_soup.insert(0, sender_name_soup)
return message_plain, str(message_soup) return message_plain, Markup(BeautifulSoup.decode(message_soup))
def build_message_payload(message: Message, sender: str | None = None) -> dict[str, str]: def build_message_payload(
message: Message, sender: str | None = None
) -> dict[str, str | Markup]:
plain = message.content plain = message.content
plain = fix_plaintext_image_urls(plain) plain = fix_plaintext_image_urls(plain)
# There's a small chance of colliding with non-Zulip URLs containing # There's a small chance of colliding with non-Zulip URLs containing
@@ -252,7 +256,7 @@ def build_message_list(
fix_spoilers_in_html(fragment, user.default_language) fix_spoilers_in_html(fragment, user.default_language)
change_katex_to_raw_latex(fragment) change_katex_to_raw_latex(fragment)
html = lxml.html.tostring(fragment, encoding="unicode") html = Markup(lxml.html.tostring(fragment, encoding="unicode"))
if sender: if sender:
plain, html = prepend_sender_to_message(plain, html, sender) plain, html = prepend_sender_to_message(plain, html, sender)
return {"plain": plain, "html": html} return {"plain": plain, "html": html}