CVE-2023-37259 (Other, published 2023-07-18T17:15Z)
Description: value: matrix-react-sdk is a react-based SDK for inserting a Matrix chat/voip client into a web page. The Export Chat feature includes certain attacker-controlled elements in the generated document without sufficient escaping, leading to stored Cross site scripting (XSS). Since the Export Chat feature generates a separate document, an attacker can only inject code run from the `null` origin, restricting the impact. However, the attacker can still potentially use the XSS to leak message contents. A malicious homeserver is a potential attacker since the affected inputs are controllable server-side. This issue has been addressed in commit `22fcd34c60` which is included in release version 3.76.0. Users are advised to upgrade. The only known workaround for this issue is to disable or to not use the Export Chat feature.
Severity: nan (CVSS v3 base score: 5.4)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/matrix-org/matrix-react-sdk
Files changed:
src/utils/exportUtils/HtmlExport.tsx
test/utils/exportUtils/HTMLExport-test.ts
Commit message: Sanitise strings going into the html export CVE-2023-37259


Vulnerable code:
        const createdText = _t("%(creatorName)s created this room.", {
            creatorName,
        });
        const exportedText = renderToStaticMarkup(
                        roomName: () => <b>{this.room.name}</b>,
                            <a href={`https://matrix.to/#/${exporter}`} target="_blank" rel="noopener noreferrer">
                                        <b>{exporterName}</b>
                                        {" (" + exporter + ")"}
                                    <b>{exporter}</b>
        const topicText = topic ? _t("Topic: %(topic)s", { topic }) : "";
                                title="${this.room.name}"
                                ${this.room.name}
                            <div class="mx_RoomHeader_topic" dir="auto"> ${topic} </div>
                                        <h2> ${this.room.name} </h2>
                                        <p> ${createdText} <br/><br/> ${exportedText} </p>
                                        <p> ${topicText} </p>

Fixed code:
import escapeHtml from "escape-html";
        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(
                        roomName: () => <b>{safeRoomName}</b>,
                            <a
                                href={`https://matrix.to/#/${encodeURIComponent(exporter)}`}
                                target="_blank"
                                rel="noopener noreferrer"
                            >
                                        <b>{escapeHtml(exporterName)}</b>I {" (" + safeExporter + ")"}
                                    <b>{safeExporter}</b>
        const safeTopicText = topic ? _t("Topic: %(topic)s", { topic: safeTopic }) : "";
                                title="${safeRoomName}"
                                ${safeRoomName}
                            <div class="mx_RoomHeader_topic" dir="auto"> ${safeTopic} </div>
                                        <h2> ${safeRoomName} </h2>
                                        <p> ${safeCreatedText} <br/><br/> ${safeExportedText} </p>
                                        <p> ${safeTopicText} </p>
import escapeHtml from "escape-html";

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