From d56b2e85aacb9744649562c5b8a8cba22f186c6c Mon Sep 17 00:00:00 2001 From: Osama Sayegh Date: Wed, 17 Mar 2021 16:11:40 +0300 Subject: [PATCH] FIX: Escape Font Awesome icons (#12421) This is not a security issue because regular users are not allowed to insert FA icons anywhere in the app. Admins can insert icons via custom badges, but they do have the ability to create themes with JS. --- .../discourse-common/addon/lib/escape.js | 32 +++++++++++++++++ .../addon/lib/icon-library.js | 23 +++++++------ .../tests/unit/lib/icon-library-test.js | 12 +++++++ .../pretty-text/addon/sanitizer.js | 34 ++----------------- lib/pretty_text.rb | 1 + 5 files changed, 60 insertions(+), 42 deletions(-) create mode 100644 app/assets/javascripts/discourse-common/addon/lib/escape.js diff --git a/app/assets/javascripts/discourse-common/addon/lib/escape.js b/app/assets/javascripts/discourse-common/addon/lib/escape.js new file mode 100644 index 00000000000..6b7da6a67f0 --- /dev/null +++ b/app/assets/javascripts/discourse-common/addon/lib/escape.js @@ -0,0 +1,32 @@ +const ESCAPE_REPLACEMENTS = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`", +}; +const BAD_CHARS = /[&<>"'`]/g; +const POSSIBLE_CHARS = /[&<>"'`]/; + +function escapeChar(chr) { + return ESCAPE_REPLACEMENTS[chr]; +} + +export default function escape(string) { + if (string === null) { + return ""; + } else if (!string) { + return string + ""; + } + + // Force a string conversion as this will be done by the append regardless and + // the regex test will do this transparently behind the scenes, causing issues if + // an object's to string has escaped characters in it. + string = "" + string; + + if (!POSSIBLE_CHARS.test(string)) { + return string; + } + return string.replace(BAD_CHARS, escapeChar); +} diff --git a/app/assets/javascripts/discourse-common/addon/lib/icon-library.js b/app/assets/javascripts/discourse-common/addon/lib/icon-library.js index 5f26ea7708a..8a01d633f02 100644 --- a/app/assets/javascripts/discourse-common/addon/lib/icon-library.js +++ b/app/assets/javascripts/discourse-common/addon/lib/icon-library.js @@ -2,6 +2,7 @@ import I18n from "I18n"; import attributeHook from "discourse-common/lib/attribute-hook"; import { h } from "virtual-dom"; import { isDevelopment } from "discourse-common/config/environment"; +import escape from "discourse-common/lib/escape"; const SVG_NAMESPACE = "http://www.w3.org/2000/svg"; let _renderers = []; @@ -140,25 +141,24 @@ registerIconRenderer({ name: "font-awesome", string(icon, params) { - const id = handleIconId(icon); - let html = ``; if (params.label) { - html += `${params.label}`; + html += `${escape(params.label)}`; } if (params.title) { - html = `${html}`; + html = `${html}`; } if (params.translatedtitle) { - html = `${html}`; } return html; @@ -176,7 +176,10 @@ registerIconRenderer({ }, [ h("use", { - "xlink:href": attributeHook("http://www.w3.org/1999/xlink", `#${id}`), + "xlink:href": attributeHook( + "http://www.w3.org/1999/xlink", + `#${escape(id)}` + ), namespace: SVG_NAMESPACE, }), ] diff --git a/app/assets/javascripts/discourse/tests/unit/lib/icon-library-test.js b/app/assets/javascripts/discourse/tests/unit/lib/icon-library-test.js index 3089ebec8fa..de03fa9c142 100644 --- a/app/assets/javascripts/discourse/tests/unit/lib/icon-library-test.js +++ b/app/assets/javascripts/discourse/tests/unit/lib/icon-library-test.js @@ -24,4 +24,16 @@ module("Unit | Utility | icon-library", function () { const iconC = convertIconClass(" fab fa-facebook "); assert.ok(iconHTML(iconC).indexOf(" ") === -1, "trims whitespace"); }); + + test("escape icon names, classes and titles", function (assert) { + const html = iconHTML("'", { + translatedtitle: "'