mirror of https://github.com/vector-im/riot-web
Sanitise strings going into the html export CVE-2023-37259
parent
d8dcfc96cc
commit
22fcd34c60
|
@ -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>`
|
||||
: ""
|
||||
}
|
||||
|
|
|
@ -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)}`);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue