From 9eea7b48988ff4ec053cc7cde964729ff9e20114 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Thu, 28 Sep 2023 12:36:29 +0000 Subject: [PATCH] fix: escape angle brackets in attribute value Angle brackets inside attribute values are now escaped, This is to ensure unsafe behaviour after de-serialization. This also is done in Chrome see: https://chromestatus.com/feature/5083926074228736 --- lib/NodeUtils.js | 54 ++++++++++++++++++++++++++-------------- test/domino.js | 2 +- test/html5lib-tests.json | 12 ++++----- test/xss.js | 11 ++++++++ 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/lib/NodeUtils.js b/lib/NodeUtils.js index a0bf00e..b736892 100644 --- a/lib/NodeUtils.js +++ b/lib/NodeUtils.js @@ -55,31 +55,49 @@ var extraNewLine = { */ }; +const ESCAPE_REGEXP = /[&<>\u00A0]/g; +const ESCAPE_ATTR_REGEXP = /[&"<>\u00A0]/g; + function escape(s) { - return s.replace(/[&<>\u00A0]/g, function(c) { - switch(c) { - case '&': return '&'; - case '<': return '<'; - case '>': return '>'; - case '\u00A0': return ' '; + if (!ESCAPE_REGEXP.test(s)) { + // nothing to do, fast path + return s; + } + + return s.replace(ESCAPE_REGEXP, (c) => { + switch (c) { + case "&": + return "&"; + case "<": + return "<"; + case ">": + return ">"; + case "\u00A0": + return " "; } }); } function escapeAttr(s) { - var toEscape = /[&"\u00A0]/g; - if (!toEscape.test(s)) { - // nothing to do, fast path - return s; - } else { - return s.replace(toEscape, function(c) { - switch(c) { - case '&': return '&'; - case '"': return '"'; - case '\u00A0': return ' '; - } - }); + if (!ESCAPE_ATTR_REGEXP.test(s)) { + // nothing to do, fast path + return s; } + + return s.replace(ESCAPE_ATTR_REGEXP, (c) => { + switch (c) { + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case '"': + return """; + case "\u00A0": + return " "; + } + }); } function attrname(a) { diff --git a/test/domino.js b/test/domino.js index 51b106e..90a09d8 100644 --- a/test/domino.js +++ b/test/domino.js @@ -399,7 +399,7 @@ exports.outerHTML = function() { // see https://github.com/whatwg/html/issues/944 //'
\n\na\n
', '

\nOne\n2 & 3

', - '' + `` ]; tests.forEach(function(html) { var d = domino.createDocument(html); diff --git a/test/html5lib-tests.json b/test/html5lib-tests.json index d462147..d650c00 100644 --- a/test/html5lib-tests.json +++ b/test/html5lib-tests.json @@ -8319,7 +8319,7 @@ ] } ], - "html": "
YY\">
", + "html": "
", "noQuirksBodyHtml": "
YY\">
" } }, @@ -8715,7 +8715,7 @@ ] } ], - "html": "
YY\">
", + "html": "
", "noQuirksBodyHtml": "
YY\">
" } }, @@ -8758,7 +8758,7 @@ ] } ], - "html": "
\">
", + "html": "
", "noQuirksBodyHtml": "
\">
" } }, @@ -8801,7 +8801,7 @@ ] } ], - "html": "
\">
", + "html": "
", "noQuirksBodyHtml": "
\">
" } }, @@ -8844,7 +8844,7 @@ ] } ], - "html": "
\">
", + "html": "
", "noQuirksBodyHtml": "
\">
" } }, @@ -80689,4 +80689,4 @@ } } ] -} \ No newline at end of file +} diff --git a/test/xss.js b/test/xss.js index a1513b6..9fa9f72 100644 --- a/test/xss.js +++ b/test/xss.js @@ -83,3 +83,14 @@ exports.fp170_37 = function() { '

' ); }; + +exports.escapeAngleBracketsInDivAttr = function() { + var document = domino.createDocument( + `
You don't have JS! Clickhere to go to the no-js website.
` + ); + // Ensure that HTML entities are properly encoded inside