CVE-2023-47119 (Ruby, published 2023-11-10T15:15Z)
Description: value: Discourse is an open source platform for community discussion. Prior to version 3.1.3 of the `stable` branch and version 3.2.0.beta3 of the `beta` and `tests-passed` branches, some links can inject arbitrary HTML tags when rendered through our Onebox engine. The issue is patched in version 3.1.3 of the `stable` branch and version 3.2.0.beta3 of the `beta` and `tests-passed` branches. 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:
lib/onebox/engine/github_issue_onebox.rb
lib/onebox/templates/discourse_category_onebox.mustache
spec/fixtures/onebox/github_issue_onebox.response
spec/lib/onebox/engine/github_issue_onebox_spec.rb
Commit message: SECURITY: Onebox templates' HTML injections.

The use of triple-curlies on Mustache templates opens the possibility for HTML injections.


Vulnerable code:
        labels = raw["labels"].map { |l| { name: Emoji.codes_to_img(l["name"]) } }
          <span class="badge-category-bg" style="background-color: #{{{color}}}"></span>
        <span class="clear-badge"><span>{{{name}}}</span></span>
          <p>{{{description}}}</p>
              <span class="badge-category-bg" style="background-color: #{{{color}}}"></span>
              <span class="badge-category clear-badge"><span class="category-name">{{{name}}}</span></span>

Fixed code:
        labels = raw["labels"].map { |l| { name: Emoji.codes_to_img(CGI.escapeHTML(l["name"])) } }
          <span class="badge-category-bg" style="background-color: #{{color}}"></span>
        <span class="clear-badge"><span>{{name}}</span></span>
          <p>{{description}}</p>
              <span class="badge-category-bg" style="background-color: #{{color}}"></span>
              <span class="badge-category clear-badge"><span class="category-name">{{name}}</span></span>
{
  "url": "https://api.github.com/repos/romanrizzi/avalancha-parser/issues/2",
  "repository_url": "https://api.github.com/repos/romanrizzi/avalancha-parser",
  "labels_url": "https://api.github.com/repos/romanrizzi/avalancha-parser/issues/2/labels{/name}",
  "comments_url": "https://api.github.com/repos/romanrizzi/avalancha-parser/issues/2/comments",
  "events_url": "https://api.github.com/repos/romanrizzi/avalancha-parser/issues/2/events",
  "html_url": "https://github.com/romanrizzi/avalancha-parser/issues/2",
  "id": 1957276127,
  "node_id": "I_kwDOEdKQe850qanf",
  "number": 2,
  "title": "Test issue #2",
  "user": {
    "login": "romanrizzi",
    "id": 5025816,
    "node_id": "MDQ6VXNlcjUwMjU4MTY=",
    "avatar_url": "https://avatars.githubusercontent.com/u/5025816?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/romanrizzi",
    "html_url": "https://github.com/romanrizzi",
    "followers_url": "https://api.github.com/users/romanrizzi/followers",
    "following_url": "https://api.github.com/users/romanrizzi/following{/other_user}",
    "gists_url": "https://api.github.com/users/romanrizzi/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/romanrizzi/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/romanrizzi/subscriptions",
    "organizations_url": "https://api.github.com/users/romanrizzi/orgs",
    "repos_url": "https://api.github.com/users/romanrizzi/repos",
    "events_url": "https://api.github.com/users/romanrizzi/events{/privacy}",
    "received_events_url": "https://api.github.com/users/romanrizzi/received_events",
    "type": "User",
    "site_admin": false
  },
  "labels": [
    {
      "id": 6119137046,
      "node_id": "LA_kwDOEdKQe88AAAABbLqfFg",
      "url": "https://api.github.com/repos/romanrizzi/avalancha-parser/labels/Test%20:+1:%20%3Cstyle%3Ebody%20%7Bdisplay:%20none%7D%3C/style%3E",
      "name": "Test :+1: <style>body {display: none}</style>",
      "color": "0E8A16",
      "default": false,
      "description": ""
    }
  ],
  "state": "open",
  "locked": false,
  "assignee": null,
  "assignees": [

  ],
  "milestone": null,
  "comments": 0,
  "created_at": "2023-10-23T14:13:07Z",
  "updated_at": "2023-10-23T14:13:07Z",
  "closed_at": null,
  "author_association": "OWNER",
  "active_lock_reason": null,
  "body": "test",
  "closed_by": null,
  "reactions": {
    "url": "https://api.github.com/repos/romanrizzi/avalancha-parser/issues/2/reactions",
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
  },
  "timeline_url": "https://api.github.com/repos/romanrizzi/avalancha-parser/issues/2/timeline",
  "performed_via_github_app": null,
  "state_reason": null
}
# frozen_string_literal: true

RSpec.describe Onebox::Engine::GithubIssueOnebox do
  before do
    @link = "https://github.com/discourse/discourse/issues/1"

    stub_request(:get, "https://api.github.com/repos/discourse/discourse/issues/1").to_return(
      status: 200,
      body: onebox_response("github_issue_onebox"),
    )
  end

  include_context "with engines"
  it_behaves_like "an engine"

  describe "#to_html" do
    it "sanitizes the input and transform the emoji into an img tag" do
      sanitized_label =
        'Test <img src="/images/emoji/twitter/+1.png?v=12" title="+1" class="emoji" alt="+1" loading="lazy" width="20" height="20"> &lt;style&gt;body {display: none}&lt;/style&gt;'

      expect(html).to include(sanitized_label)
    end
  end
end