Home Discourse CVE-2023-47119 - Building a CVE POC from commits changes
Post
Cancel

Discourse CVE-2023-47119 - Building a CVE POC from commits changes

I was checking for some Discourse vulnerabilities, and I saw that a new CVE was dropped on 11/10/2023 CVE-2023-47119

The details didn’t mention any POC, so I did some analysis based on the source code commits changes to understand the vulnerability and how it is possible to exploit it.

The article includes details, lab setup and a demo.

A GitHub repository was created for the POC https://github.com/BaadMaro/CVE-2023-47119 Feel free to contribute with reports, escalations, and links to other POCs too.

CVE-2023-47119

CVE-2023-47119 is a new Discourse vulnerability affecting versions 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 the Onebox engine.

The severity is Medium 5.3 which is understandable as the vulnerability is only HTML injection and it needs a bypass for the XSS filter used by Discourse to cause a bigger impact.

Checking the CVE details CVE-2023-47119, we can see the commits added for the fix for example this one : https://github.com/discourse/discourse/commit/628b293ff53fb617b3464dd27268aec84388cc09

The interesting part is the fix for the github_issue_onebox.rb file which reveals our target.

image

As we can see :

  • The bug is affecting /lib/onebox/engine/github_issue_onebox.rb
  • An escape was added to the value of GitHub issue label.
  • If we check the details of the file, we can see an emoji converter that converts the emoji code to an image. The code is described in /app/helpers/emoji_helper.rb which is a call for /app/models/emoji.rb
1
2
3
4
5
module EmojiHelper
  def emoji_codes_to_img(str)
    raw(Emoji.codes_to_img(str))
  end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  def self.codes_to_img(str)
    return if str.blank?

    str =
      str.gsub(/:([\w\-+]*(?::t\d)?):/) do |name|
        code = $1

        if code && Emoji.custom?(code)
          emoji = Emoji[code]
          "<img src=\"#{emoji.url}\" title=\"#{code}\" class=\"emoji\" alt=\"#{code}\" loading=\"lazy\" width=\"20\" height=\"20\">"
        elsif code && Emoji.exists?(code)
          "<img src=\"#{Emoji.url_for(code)}\" title=\"#{code}\" class=\"emoji\" alt=\"#{code}\" loading=\"lazy\" width=\"20\" height=\"20\">"
        else
          name
        end
      end

The function returns an image HTML element for known emojis, and if it doesn’t exist, it returns the same text used.

In /lib/onebox/engine/github_issue_onebox.rb before the fix, we can see the emoji function is used in label part :

1
labels = raw["labels"].map { |l| { name: Emoji.codes_to_img(l["name"]) } }

The label is an identifier used by Github issues to specify, for example the type of issue.

In Discourse, the onboxe engine used for topic details and replies have a custom engine for GitHub issues which pulls the issue details via URL and converts them to a better view.

Lab setup

To build Discourse 3.1.3 which is a vulnerable version, I used the docker compose file by bitnami https://hub.docker.com/r/bitnami/discourse/

  • docker-compose.yml : https://raw.githubusercontent.com/bitnami/containers/main/bitnami/discourse/docker-compose.yml
  • Modify the 2 images tag to 3.1.3 or any other vulnerable version docker.io/bitnami/discourse:3.1.3
  • Change host to your preferred config like 0.0.0.0 or your internal network IP address DISCOURSE_HOST=0.0.0.0
  • You can also modify the port 80
  • After modifying the file, run docker-compose up -d
  • Few minutes you’ll be able to see the discourse web server at your host port 80
  • App default login user:bitnami123

You can also use the official docker https://github.com/discourse/discourse_docker

Demo

To control the label name, we can create a repository with an issue and modify the label name assigned to the issue.

Example

image

image

image

In this example, the issue label was “bug”. We can now try including an emoji like :smile:

image

1
2
3
<span style="display:inline-block;margin-top:2px;background-color: #B8B8B8;padding: 2px;border-radius: 4px;color: #fff;margin-left: 3px;">
          bug <img src="/images/emoji/twitter/smile.png?v=12" title="smile" class="emoji" alt="smile" width="20" height="20">
        </span>

As we can see, it’s the same discussed output from the emoji function.

Now let’s confirm a no-existing emoji

image

1
2
3
<span style="display:inline-block;margin-top:2px;background-color: #B8B8B8;padding: 2px;border-radius: 4px;color: #fff;margin-left: 3px;">
          bug :baadmaroemoji:
        </span>

As the emoji function didn’t find the emoji, it returned the original text.

The returned text is not sanitized (emoji format or just simple text) which is the cause of the CVE.

We can confirm by injecting an h1 tag for example. Having the double “:” in label name is not necessary

image

image

image

1
2
3
<span style="display:inline-block;margin-top:2px;background-color: #B8B8B8;padding: 2px;border-radius: 4px;color: #fff;margin-left: 3px;">
          bug <h1>BaadMaro HTML Injection POC</h1>
        </span>

XSS Filters

https://github.com/discourse/discourse/blob/main/docs/SECURITY.md#xss

Discourse is using some mechanisms to protect against XSS :

  • Node module xss https://jsxss.com/en/index.html
  • Server side allow list sanitizer using the Sanitize gem. See the relevant Discourse code.
  • Titles and all other places where non-admins can enter code are protected either using the Handlebars library or standard Rails XSS protection.
  • CSP

So to be able to escalate the CVE from HTML injection to XSS, you need a bypass for the used filters.

Conclusion

A GitHub repository was created for the POC : https://github.com/BaadMaro/CVE-2023-47119

You can contribute to the repository with reports, escalations and links to other POCs too.

Thank you.

This post is licensed under CC BY 4.0 by the author.

Bypass captcha using OCR on Dolibarr login page

DGSSI CTF PRO 2024 - Italy Writeup