'use strict'; var domino = require('../lib'); var puppeteer = require("puppeteer"); var NodeUtils = require('../lib/NodeUtils'); exports = exports.xss = {}; // Tests for HTML serialization concentrating on possible "Mutation based // XSS vectors"; see https://cure53.de/fp170.pdf // If we change HTML serialization such that any of these tests fail, please // review the change very carefully for potential XSS vectors! async function alertFired(html) { let alerted = false; const page = await incognito.newPage(); page.on("dialog", async dialog => { alerted = true; await dialog.accept(); }); await page.goto("data:text/html," + html, {waitUntil: 'load'}); return alerted; } /** @type {puppeteer.Browser} */ let browser; /** @type {puppeteer.BrowserContext} */ let incognito; exports.before = async function() { browser = await puppeteer.launch({headless:"new"}); incognito = await browser.createIncognitoBrowserContext(); } exports.after = async function() { await incognito.close(); await browser.close(); } exports.fp170_31 = function() { var document = domino.createDocument( '``onload=xss()' ); // In particular, ensure alt attribute is quoted, not: ...alt=``onload=xss() document.body.innerHTML.should.equal( '``onload=xss()' ); }; exports.fp170_32 = function() { var document = domino.createDocument( '
123' ); // XXX check XML serialization as well, once that's implemented // In particular, ensure that the xmlns string isn't used as an XML prefix // when serializing (and, of course, that attribute value is quoted) document.body.innerHTML.should.equal( '
123
' ); }; exports.fp170_33 = function() { var document = domino.createDocument( '

' ); // Be sure domino doesn't decode the backslash escapes // (especially in the future if we parse the CSS values more fully) document.body.innerHTML.should.equal( '

' ); }; exports.fp170_34 = function() { var document = domino.createDocument( '

' ); // Be sure domino re-encodes the entities correctly // (especially in the future if we parse the CSS values more fully) document.body.innerHTML.should.equal( '

' ); }; exports.fp170_35 = function() { var document = domino.createDocument( '' ); // Again, ensure domino doesn't decode the backslash escapes // (especially in the future if we parse the CSS values more fully) document.body.innerHTML.should.equal( '' ); }; exports.fp170_36 = function() { var document = domino.createDocument( '' ); // Ensure that HTML entities are properly encoded inside ' ); }; exports.fp170_37 = function() { var document = domino.createDocument( '

' ); // Ensure that HTML entities are properly encoded inside

' ); }; exports.escapeAngleBracketsInDivAttr = function() { var document = domino.createDocument( `
You don't have JS! Clickhere to go to the no-js website.
` ); document.body.innerHTML.should.equal( `
You don't have JS! Clickhere to go to the no-js website.
` ); }; exports.escapeAngleBracketsInNoScriptAttr = function() { var document = domino.createDocument( `
` ); document.body.innerHTML.should.equal( `
` ); }; exports.styleMatchingClosingTagInRawText = function() { const document = domino.createDocument(''); const style = document.createElement("style"); style.textContent = "abc"; document.body.appendChild(style); // Ensure that HTML entities are properly encoded inside ' ); const html = document.serialize(); return alertFired(html).should.eventually.be.false('alert fired for: ' + html); }; exports.styleMatchingClosingTagSkipsInsideCommentedContent = function() { const document = domino.createDocument(''); const style = document.createElement("style"); style.textContent = "abc"; document.body.appendChild(style); document.body.serialize().should.equal( '' ); const html = document.serialize(); return alertFired(html).should.eventually.be.false('alert fired for: ' + html); }; exports.styleMatchingClosingTagAfterClosingComment = function() { const document = domino.createDocument(''); const style = document.createElement("style"); style.textContent = "abc-->"; document.body.appendChild(style); // Ensure that HTML entities are properly encoded inside ' ); const html = document.serialize(); return alertFired(html).should.eventually.be.false('alert fired for: ' + html); }; exports.styleMatchingClosingTagSkipsUnclosedCommentedContent = function() { const document = domino.createDocument(''); const style = document.createElement("style"); style.textContent = "abc' ); const html = document.serialize(); return alertFired(html).should.eventually.be.false('alert fired for: ' + html); } exports.alternativeEndTagForRawTextTag = function() { const document = domino.createDocument(''); const style = document.createElement("style"); style.textContent = ""; document.body.appendChild(style); document.body.serialize().should.equal( '' ); const html = document.serialize(); return alertFired(html).should.eventually.be.false('alert fired for: ' + html); } exports.badCommentNode = function() { const document = domino.createDocument(''); const comment = document.createComment('-->'); document.body.appendChild(comment); document.body.serialize().should.equal( '' ); const html = document.serialize(); return alertFired(html).should.eventually.be.false('alert fired for: ' + html); } exports.anotherBadCommentNode = function() { const document = domino.createDocument(''); const comment = document.createComment('--!>'); document.body.appendChild(comment); document.body.serialize().should.equal( '' ); const html = document.serialize(); return alertFired(html).should.eventually.be.false('alert fired for: ' + html); } exports.badProcessingInstruction = function() { const document = domino.createDocument(''); const pi = document.createProcessingInstruction("bad", ">"); document.body.appendChild(pi); document.body.serialize().should.equal( '' ); const html = document.serialize(); return alertFired(html).should.eventually.be.false('alert fired for: ' + html); } exports.verifyEscapeMatchingClosingTag = function() { const cases = [ ['', 'style', ''], // no artifacts while processing an empty string ['abc', 'script', 'abc'], // no artifacts while processing a string without closing tags ['abc', 'style', '</style /foobar>abc'], ['', 'xmp', '</xmp>'], ['""', 'xmp', '"</xmp>"'], // Raw content element inside another raw content element. ['</style><script>alert(1)</script>', 'style', '&lt;/style><script>alert(1)</script>'], ['abc', 'iframe', '</style><script>alert(1)</script>'], ]; for (const [rawContent, parentTag, expected] of cases) { NodeUtils.ɵescapeMatchingClosingTag(rawContent, parentTag).should.equal(expected); } } exports.verifyEscapeClosingCommentTag = function() { const cases = [ ['', ''], // no artifacts while processing an empty string ['abc', 'abc'], // no artifacts while processing a string without closing tags ['a-->bc-->', 'a-->bc-->'], ['a--!>bc--!>', 'a--!>bc--!>'], ['a- -> b c - ->', 'a- -> b c - ->'], ['a- -!> b c - -!>', 'a- -!> b c - -!>'], [' ', ' ', '', '