Sanitise strings going into the html export CVE-2023-37259

t3chguy/dedup-icons-17oct
RiotRobot 2023-07-18 13:23:27 +01:00
parent d8dcfc96cc
commit 22fcd34c60
2 changed files with 71 additions and 16 deletions

View File

@ -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(
<p>
{_t(
"This is the start of export of <roomName/>. Exported by <exporterDetails/> at %(exportDate)s.",
@ -109,16 +115,19 @@ export default class HTMLExporter extends Exporter {
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>
),
@ -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" }}>
@ -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}
@ -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>`
: ""
}

View File

@ -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 = "<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)}`);
});
});