Skip to content

Commit 22fcd34

Browse files
committed
Sanitise strings going into the html export CVE-2023-37259
1 parent d8dcfc9 commit 22fcd34

File tree

2 files changed

+71
-16
lines changed

2 files changed

+71
-16
lines changed

src/utils/exportUtils/HtmlExport.tsx

+25-16
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event";
2121
import { renderToStaticMarkup } from "react-dom/server";
2222
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
2323
import { logger } from "matrix-js-sdk/src/logger";
24+
import escapeHtml from "escape-html";
2425

2526
import Exporter from "./Exporter";
2627
import { mediaFromMxc } from "../../customisations/Media";
@@ -97,28 +98,36 @@ export default class HTMLExporter extends Exporter {
9798
const exporter = this.room.client.getSafeUserId();
9899
const exporterName = this.room.getMember(exporter)?.rawDisplayName;
99100
const topic = this.room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || "";
100-
const createdText = _t("%(creatorName)s created this room.", {
101-
creatorName,
102-
});
103101

104-
const exportedText = renderToStaticMarkup(
102+
const safeCreatedText = escapeHtml(
103+
_t("%(creatorName)s created this room.", {
104+
creatorName,
105+
}),
106+
);
107+
const safeExporter = escapeHtml(exporter);
108+
const safeRoomName = escapeHtml(this.room.name);
109+
const safeTopic = escapeHtml(topic);
110+
const safeExportedText = renderToStaticMarkup(
105111
<p>
106112
{_t(
107113
"This is the start of export of <roomName/>. Exported by <exporterDetails/> at %(exportDate)s.",
108114
{
109115
exportDate,
110116
},
111117
{
112-
roomName: () => <b>{this.room.name}</b>,
118+
roomName: () => <b>{safeRoomName}</b>,
113119
exporterDetails: () => (
114-
<a href={`https://matrix.to/#/${exporter}`} target="_blank" rel="noopener noreferrer">
120+
<a
121+
href={`https://matrix.to/#/${encodeURIComponent(exporter)}`}
122+
target="_blank"
123+
rel="noopener noreferrer"
124+
>
115125
{exporterName ? (
116126
<>
117-
<b>{exporterName}</b>
118-
{" (" + exporter + ")"}
127+
<b>{escapeHtml(exporterName)}</b>I {" (" + safeExporter + ")"}
119128
</>
120129
) : (
121-
<b>{exporter}</b>
130+
<b>{safeExporter}</b>
122131
)}
123132
</a>
124133
),
@@ -127,7 +136,7 @@ export default class HTMLExporter extends Exporter {
127136
</p>,
128137
);
129138

130-
const topicText = topic ? _t("Topic: %(topic)s", { topic }) : "";
139+
const safeTopicText = topic ? _t("Topic: %(topic)s", { topic: safeTopic }) : "";
131140
const previousMessagesLink = renderToStaticMarkup(
132141
currentPage !== 0 ? (
133142
<div style={{ textAlign: "center" }}>
@@ -183,12 +192,12 @@ export default class HTMLExporter extends Exporter {
183192
<div
184193
dir="auto"
185194
class="mx_RoomHeader_nametext"
186-
title="${this.room.name}"
195+
title="${safeRoomName}"
187196
>
188-
${this.room.name}
197+
${safeRoomName}
189198
</div>
190199
</div>
191-
<div class="mx_RoomHeader_topic" dir="auto"> ${topic} </div>
200+
<div class="mx_RoomHeader_topic" dir="auto"> ${safeTopic} </div>
192201
</div>
193202
</div>
194203
${previousMessagesLink}
@@ -214,10 +223,10 @@ export default class HTMLExporter extends Exporter {
214223
currentPage == 0
215224
? `<div class="mx_NewRoomIntro">
216225
${roomAvatar}
217-
<h2> ${this.room.name} </h2>
218-
<p> ${createdText} <br/><br/> ${exportedText} </p>
226+
<h2> ${safeRoomName} </h2>
227+
<p> ${safeCreatedText} <br/><br/> ${safeExportedText} </p>
219228
<br/>
220-
<p> ${topicText} </p>
229+
<p> ${safeTopicText} </p>
221230
</div>`
222231
: ""
223232
}

test/utils/exportUtils/HTMLExport-test.ts

+46
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
RoomState,
2626
} from "matrix-js-sdk/src/matrix";
2727
import fetchMock from "fetch-mock-jest";
28+
import escapeHtml from "escape-html";
2829

2930
import { filterConsole, mkStubRoom, REPEATABLE_DATE, stubClient } from "../../test-utils";
3031
import { ExportType, IExportOptions } from "../../../src/utils/exportUtils/exportUtils";
@@ -505,4 +506,49 @@ describe("HTMLExport", () => {
505506
);
506507
expect(result).not.toContain("Next group of messages");
507508
});
509+
510+
it("should not leak javascript from room names or topics", async () => {
511+
const name = "<svg onload=alert(3)>";
512+
const topic = "<svg onload=alert(5)>";
513+
mockMessages(EVENT_MESSAGE);
514+
room.currentState.setStateEvents([
515+
new MatrixEvent({
516+
type: EventType.RoomName,
517+
event_id: "$00001",
518+
room_id: room.roomId,
519+
sender: "@alice:example.com",
520+
origin_server_ts: 0,
521+
content: { name },
522+
state_key: "",
523+
}),
524+
new MatrixEvent({
525+
type: EventType.RoomTopic,
526+
event_id: "$00002",
527+
room_id: room.roomId,
528+
sender: "@alice:example.com",
529+
origin_server_ts: 1,
530+
content: { topic },
531+
state_key: "",
532+
}),
533+
]);
534+
room.recalculate();
535+
536+
const exporter = new HTMLExporter(
537+
room,
538+
ExportType.Timeline,
539+
{
540+
attachmentsIncluded: false,
541+
maxSize: 1_024 * 1_024,
542+
},
543+
() => {},
544+
);
545+
546+
await exporter.export();
547+
const html = await getMessageFile(exporter).text();
548+
549+
expect(html).not.toContain(`${name}`);
550+
expect(html).toContain(`${escapeHtml(name)}`);
551+
expect(html).not.toContain(`${topic}`);
552+
expect(html).toContain(`Topic: ${escapeHtml(topic)}`);
553+
});
508554
});

0 commit comments

Comments
 (0)