From 22fcd34c606f32129ebc967fc21f24fb708a98b8 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 18 Jul 2023 13:23:27 +0100 Subject: [PATCH] Sanitise strings going into the html export CVE-2023-37259 --- src/utils/exportUtils/HtmlExport.tsx | 41 ++++++++++++-------- test/utils/exportUtils/HTMLExport-test.ts | 46 +++++++++++++++++++++++ 2 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/utils/exportUtils/HtmlExport.tsx b/src/utils/exportUtils/HtmlExport.tsx index 667978b7b0..41edfd93df 100644 --- a/src/utils/exportUtils/HtmlExport.tsx +++ b/src/utils/exportUtils/HtmlExport.tsx @@ -21,6 +21,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { renderToStaticMarkup } from "react-dom/server"; import { EventType, MsgType } from "matrix-js-sdk/src/@types/event"; import { logger } from "matrix-js-sdk/src/logger"; +import escapeHtml from "escape-html"; import Exporter from "./Exporter"; import { mediaFromMxc } from "../../customisations/Media"; @@ -97,11 +98,16 @@ export default class HTMLExporter extends Exporter { const exporter = this.room.client.getSafeUserId(); const exporterName = this.room.getMember(exporter)?.rawDisplayName; const topic = this.room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || ""; - const createdText = _t("%(creatorName)s created this room.", { - creatorName, - }); - const exportedText = renderToStaticMarkup( + const safeCreatedText = escapeHtml( + _t("%(creatorName)s created this room.", { + creatorName, + }), + ); + const safeExporter = escapeHtml(exporter); + const safeRoomName = escapeHtml(this.room.name); + const safeTopic = escapeHtml(topic); + const safeExportedText = renderToStaticMarkup(

{_t( "This is the start of export of . Exported by at %(exportDate)s.", @@ -109,16 +115,19 @@ export default class HTMLExporter extends Exporter { exportDate, }, { - roomName: () => {this.room.name}, + roomName: () => {safeRoomName}, exporterDetails: () => ( - + {exporterName ? ( <> - {exporterName} - {" (" + exporter + ")"} + {escapeHtml(exporterName)}I {" (" + safeExporter + ")"} ) : ( - {exporter} + {safeExporter} )} ), @@ -127,7 +136,7 @@ export default class HTMLExporter extends Exporter {

, ); - const topicText = topic ? _t("Topic: %(topic)s", { topic }) : ""; + const safeTopicText = topic ? _t("Topic: %(topic)s", { topic: safeTopic }) : ""; const previousMessagesLink = renderToStaticMarkup( currentPage !== 0 ? (
@@ -183,12 +192,12 @@ export default class HTMLExporter extends Exporter {
- ${this.room.name} + ${safeRoomName}
-
${topic}
+
${safeTopic}
${previousMessagesLink} @@ -214,10 +223,10 @@ export default class HTMLExporter extends Exporter { currentPage == 0 ? `
${roomAvatar} -

${this.room.name}

-

${createdText}

${exportedText}

+

${safeRoomName}

+

${safeCreatedText}

${safeExportedText}


-

${topicText}

+

${safeTopicText}

` : "" } diff --git a/test/utils/exportUtils/HTMLExport-test.ts b/test/utils/exportUtils/HTMLExport-test.ts index f81764170c..53512dbad1 100644 --- a/test/utils/exportUtils/HTMLExport-test.ts +++ b/test/utils/exportUtils/HTMLExport-test.ts @@ -25,6 +25,7 @@ import { RoomState, } from "matrix-js-sdk/src/matrix"; import fetchMock from "fetch-mock-jest"; +import escapeHtml from "escape-html"; import { filterConsole, mkStubRoom, REPEATABLE_DATE, stubClient } from "../../test-utils"; import { ExportType, IExportOptions } from "../../../src/utils/exportUtils/exportUtils"; @@ -505,4 +506,49 @@ describe("HTMLExport", () => { ); expect(result).not.toContain("Next group of messages"); }); + + it("should not leak javascript from room names or topics", async () => { + const name = ""; + const topic = ""; + mockMessages(EVENT_MESSAGE); + room.currentState.setStateEvents([ + new MatrixEvent({ + type: EventType.RoomName, + event_id: "$00001", + room_id: room.roomId, + sender: "@alice:example.com", + origin_server_ts: 0, + content: { name }, + state_key: "", + }), + new MatrixEvent({ + type: EventType.RoomTopic, + event_id: "$00002", + room_id: room.roomId, + sender: "@alice:example.com", + origin_server_ts: 1, + content: { topic }, + state_key: "", + }), + ]); + room.recalculate(); + + const exporter = new HTMLExporter( + room, + ExportType.Timeline, + { + attachmentsIncluded: false, + maxSize: 1_024 * 1_024, + }, + () => {}, + ); + + await exporter.export(); + const html = await getMessageFile(exporter).text(); + + expect(html).not.toContain(`${name}`); + expect(html).toContain(`${escapeHtml(name)}`); + expect(html).not.toContain(`${topic}`); + expect(html).toContain(`Topic: ${escapeHtml(topic)}`); + }); });