Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Sanitise strings going into the html export CVE-2023-37259
Browse files Browse the repository at this point in the history
  • Loading branch information
RiotRobot committed Jul 18, 2023
1 parent d8dcfc9 commit 22fcd34
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 16 deletions.
41 changes: 25 additions & 16 deletions src/utils/exportUtils/HtmlExport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -97,28 +98,36 @@ 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(
<p>
{_t(
"This is the start of export of <roomName/>. Exported by <exporterDetails/> at %(exportDate)s.",
{
exportDate,
},
{
roomName: () => <b>{this.room.name}</b>,
roomName: () => <b>{safeRoomName}</b>,
exporterDetails: () => (
<a href={`https://matrix.to/#/${exporter}`} target="_blank" rel="noopener noreferrer">
<a
href={`https://matrix.to/#/${encodeURIComponent(exporter)}`}
target="_blank"
rel="noopener noreferrer"
>
{exporterName ? (
<>
<b>{exporterName}</b>
{" (" + exporter + ")"}
<b>{escapeHtml(exporterName)}</b>I {" (" + safeExporter + ")"}
</>
) : (
<b>{exporter}</b>
<b>{safeExporter}</b>
)}
</a>
),
Expand All @@ -127,7 +136,7 @@ export default class HTMLExporter extends Exporter {
</p>,
);

const topicText = topic ? _t("Topic: %(topic)s", { topic }) : "";
const safeTopicText = topic ? _t("Topic: %(topic)s", { topic: safeTopic }) : "";
const previousMessagesLink = renderToStaticMarkup(
currentPage !== 0 ? (
<div style={{ textAlign: "center" }}>
Expand Down Expand Up @@ -183,12 +192,12 @@ export default class HTMLExporter extends Exporter {
<div
dir="auto"
class="mx_RoomHeader_nametext"
title="${this.room.name}"
title="${safeRoomName}"
>
${this.room.name}
${safeRoomName}
</div>
</div>
<div class="mx_RoomHeader_topic" dir="auto"> ${topic} </div>
<div class="mx_RoomHeader_topic" dir="auto"> ${safeTopic} </div>
</div>
</div>
${previousMessagesLink}
Expand All @@ -214,10 +223,10 @@ export default class HTMLExporter extends Exporter {
currentPage == 0
? `<div class="mx_NewRoomIntro">
${roomAvatar}
<h2> ${this.room.name} </h2>
<p> ${createdText} <br/><br/> ${exportedText} </p>
<h2> ${safeRoomName} </h2>
<p> ${safeCreatedText} <br/><br/> ${safeExportedText} </p>
<br/>
<p> ${topicText} </p>
<p> ${safeTopicText} </p>
</div>`
: ""
}
Expand Down
46 changes: 46 additions & 0 deletions test/utils/exportUtils/HTMLExport-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 = "<svg onload=alert(3)>";
const topic = "<svg onload=alert(5)>";
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)}`);
});
});

0 comments on commit 22fcd34

Please # to comment.