I found two problems in codabench
- Where DOMPurify is used, the bundled version is 1.0.10 (August 2018), six years out of date (latest: 3.4.13), affected by every known mXSS bypass published since, including CVE-2020-26870 and CVE-2024-47875.
Known XSS bypasses affecting 1.0.10 (all fixed in later versions):
- The competition "Pages" / "Terms" / "Phase description" rendering path never sanitizes HTML at all.
renderMarkdownWithLatex() pipes raw HTML through marked (default sanitize: false) and inserts the parsed DOM nodes with appendChild. DOMPurify is not called anywhere in this path, so no bypass is needed — plain <img src=x onerror=...> or <script> in a page's content is rendered verbatim and executes for every visitor.
Code location (repo): src/static/js/ours/latex_markdown_html.js, function renderMarkdownWithLatex():
- Line 61:
let html = marked(contentWithLatexPlaceholders) — marked@3.0.7 is loaded with its default options (sanitize: false), so raw HTML in markdown passes through unchanged.
- Lines 88–91:
new DOMParser().parseFromString(html, "text/html") → returns body.childNodes.
- The file contains no reference to DOMPurify at all .
Code location (repo): src/static/riot/competitions/detail/_tabs.tag lines 373–390 — the visitor-facing competition page:
_.forEach(self.competition.pages, (page, index) => {
const rendered_content = renderMarkdownWithLatex(page.content) // line 373
$(`#page_${index}`)[0].innerHTML = "" // line 374
rendered_content.forEach(node => {
$(`#page_${index}`)[0].appendChild(node.cloneNode(true)); // live DOM insertion
});
});
// line 381: same for self.competition.terms
// line 389: same for phase.description
Also affected: the organizer page-preview modal in src/static/riot/competitions/editor/_pages.tag lines 162–163.
I found two problems in codabench
Known XSS bypasses affecting 1.0.10 (all fixed in later versions):
FORMelementsCUSTOM_ELEMENT_HANDLINGfallbacksetConfigIN_PLACEmoderenderMarkdownWithLatex()pipes raw HTML throughmarked(defaultsanitize: false) and inserts the parsed DOM nodes withappendChild. DOMPurify is not called anywhere in this path, so no bypass is needed — plain<img src=x onerror=...>or<script>in a page's content is rendered verbatim and executes for every visitor.Code location (repo):
src/static/js/ours/latex_markdown_html.js, functionrenderMarkdownWithLatex():let html = marked(contentWithLatexPlaceholders)—marked@3.0.7is loaded with its default options (sanitize: false), so raw HTML in markdown passes through unchanged.new DOMParser().parseFromString(html, "text/html")→ returnsbody.childNodes.Code location (repo):
src/static/riot/competitions/detail/_tabs.taglines 373–390 — the visitor-facing competition page:Also affected: the organizer page-preview modal in
src/static/riot/competitions/editor/_pages.taglines 162–163.