CVE-2023-26040 (Other, published 2023-03-17T15:15Z)
Description: value: Discourse is an open-source discussion platform. Between versions 3.1.0.beta2 and 3.1.0.beta3 of the `tests-passed` branch, editing or responding to a chat message containing malicious content could lead to a cross-site scripting attack. This issue is patched in version 3.1.0.beta3 of the `tests-passed` branch. There are no known workarounds.
Severity: nan (CVSS v3 base score: 6.1)
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/discourse/discourse
Files changed:
app/assets/javascripts/discourse/app/components/choose-topic.hbs
app/assets/javascripts/discourse/app/helpers/replace-emoji.js
app/assets/javascripts/discourse/app/templates/modal/insert-hyperlink.hbs
app/assets/javascripts/discourse/app/templates/review-topics.hbs
app/assets/javascripts/discourse/tests/integration/helpers/replace-emoji-test.js
app/assets/javascripts/select-kit/addon/templates/components/topic-row.hbs
plugins/chat/assets/javascripts/discourse/components/chat-channel-about-view.hbs
plugins/chat/assets/javascripts/discourse/components/chat-channel-card.hbs
plugins/chat/assets/javascripts/discourse/components/chat-channel-title.hbs
plugins/chat/spec/system/chat_channel_spec.rb
Commit message: SECURITY: XSS on chat excerpts

Non-markdown tags weren't being escaped in chat excerpts. This could be
triggered by editing a chat message containing a tag (self XSS), or by
replying to a chat message with a tag (XSS).

Co-authored-by: Jan Cernik <jancernik12@gmail.com>


Vulnerable code:
              {{replace-emoji t.fancy_title}}
import { htmlSafe } from "@ember/template";
              {{replace-emoji result.fancy_title}}
              >{{replace-emoji rt.fancy_title}}</a>
<div class="topic-title">{{replace-emoji this.item.fancy_title}}</div>
        {{replace-emoji this.channel.escapedTitle}}
          {{replace-emoji @channel.escapedTitle}}
        {{replace-emoji @channel.escapedDescription}}
        {{replace-emoji this.channel.escapedTitle}}

Fixed code:
              {{replace-emoji t.title}}
import { htmlSafe, isHTMLSafe } from "@ember/template";
import { escapeExpression } from "discourse/lib/utilities";
  text = isHTMLSafe(text) ? text.toString() : escapeExpression(text);
              {{replace-emoji result.title}}
              >{{replace-emoji rt.title}}</a>
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { render } from "@ember/test-helpers";
import { hbs } from "ember-cli-htmlbars";

module("Integration | Helper | replace-emoji", function (hooks) {
  setupRenderingTest(hooks);

  test("it replaces the emoji", async function (assert) {
    await render(hbs`<span>{{replace-emoji "some text :heart:"}}</span>`);

    assert.dom(`span`).includesText("some text");
    assert.dom(`.emoji[title="heart"]`).exists();
  });

  test("it escapes the text", async function (assert) {
    await render(
      hbs`<span>{{replace-emoji "<style>body: {background: red;}</style>"}}</span>`
    );

    assert.dom(`span`).hasText("<style>body: {background: red;}</style>");
  });

  test("it renders html-safe text", async function (assert) {
    await render(hbs`<span>{{replace-emoji (html-safe "safe text")}}</span>`);

    assert.dom(`span`).hasText("safe text");
  });
});
<div class="topic-title">{{replace-emoji this.item.title}}</div>
        {{replace-emoji this.channel.title}}
          {{replace-emoji @channel.title}}
        {{replace-emoji @channel.description}}
        {{replace-emoji this.channel.title}}
  context "when replying to message that has tags" do
    fab!(:other_user) { Fabricate(:user) }
    fab!(:message_2) do
      Fabricate(
        :chat_message,
        user: other_user,
        chat_channel: channel_1,
        message: "<mark>not marked</mark>",
      )
    end

    before do
      Fabricate(:chat_message, user: other_user, chat_channel: channel_1)
      Fabricate(:chat_message, in_reply_to: message_2, user: current_user, chat_channel: channel_1)
      channel_1.add(other_user)
      channel_1.add(current_user)
      sign_in(current_user)
    end

    it "escapes the reply-to line" do
      chat.visit_channel(channel_1)

      expect(find(".chat-reply .chat-reply__excerpt")["innerHTML"].strip).to eq(
        "&lt;mark&gt;not marked&lt;/mark&gt;",
      )
    end
  end