Skip to content

Commit

Permalink
fix: escape angle brackets in attribute value
Browse files Browse the repository at this point in the history
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
  • Loading branch information
alan-agius4 committed Sep 29, 2023
1 parent 9212032 commit 9eea7b4
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 25 deletions.
54 changes: 36 additions & 18 deletions lib/NodeUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 '&amp;';
case '<': return '&lt;';
case '>': return '&gt;';
case '\u00A0': return '&nbsp;';
if (!ESCAPE_REGEXP.test(s)) {
// nothing to do, fast path
return s;
}

return s.replace(ESCAPE_REGEXP, (c) => {
switch (c) {
case "&":
return "&amp;";
case "<":
return "&lt;";
case ">":
return "&gt;";
case "\u00A0":
return "&nbsp;";
}
});
}

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 '&amp;';
case '"': return '&quot;';
case '\u00A0': return '&nbsp;';
}
});
if (!ESCAPE_ATTR_REGEXP.test(s)) {
// nothing to do, fast path
return s;
}

return s.replace(ESCAPE_ATTR_REGEXP, (c) => {
switch (c) {
case "<":
return "&lt;";
case ">":
return "&gt;";
case "&":
return "&amp;";
case '"':
return "&quot;";
case "\u00A0":
return "&nbsp;";
}
});
}

function attrname(a) {
Expand Down
2 changes: 1 addition & 1 deletion test/domino.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ exports.outerHTML = function() {
// see https://github.com/whatwg/html/issues/944
//'<body><pre>\n\na\n</pre></body>',
'<body bgcolor="white"><h1 style="color: red">\nOne\n2 &amp; 3</h1></body>',
'<body data-test="<>&amp;&quot;\'"></body>'
`<body data-test="&lt;&gt;&amp;&quot;\'"></body>`
];
tests.forEach(function(html) {
var d = domino.createDocument(html);
Expand Down
12 changes: 6 additions & 6 deletions test/html5lib-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -8319,7 +8319,7 @@
]
}
],
"html": "<html><head></head><body><div bar=\"ZZ>YY\"></div></body></html>",
"html": "<html><head></head><body><div bar=\"ZZ&gt;YY\"></div></body></html>",
"noQuirksBodyHtml": "<div bar=\"ZZ>YY\"></div>"
}
},
Expand Down Expand Up @@ -8715,7 +8715,7 @@
]
}
],
"html": "<html><head></head><body><div bar=\"ZZ> YY\"></div></body></html>",
"html": "<html><head></head><body><div bar=\"ZZ&gt; YY\"></div></body></html>",
"noQuirksBodyHtml": "<div bar=\"ZZ> YY\"></div>"
}
},
Expand Down Expand Up @@ -8758,7 +8758,7 @@
]
}
],
"html": "<html><head></head><body><div bar=\"ZZ>\"></div></body></html>",
"html": "<html><head></head><body><div bar=\"ZZ&gt;\"></div></body></html>",
"noQuirksBodyHtml": "<div bar=\"ZZ>\"></div>"
}
},
Expand Down Expand Up @@ -8801,7 +8801,7 @@
]
}
],
"html": "<html><head></head><body><div bar=\"ZZ>\"></div></body></html>",
"html": "<html><head></head><body><div bar=\"ZZ&gt;\"></div></body></html>",
"noQuirksBodyHtml": "<div bar=\"ZZ>\"></div>"
}
},
Expand Down Expand Up @@ -8844,7 +8844,7 @@
]
}
],
"html": "<html><head></head><body><div bar=\"ZZ>\"></div></body></html>",
"html": "<html><head></head><body><div bar=\"ZZ&gt;\"></div></body></html>",
"noQuirksBodyHtml": "<div bar=\"ZZ>\"></div>"
}
},
Expand Down Expand Up @@ -80689,4 +80689,4 @@
}
}
]
}
}
11 changes: 11 additions & 0 deletions test/xss.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,14 @@ exports.fp170_37 = function() {
'<p><svg><style>*{font-family:\'&lt;/style&gt;&lt;img/src=x\tonerror=xss()//\'}</style></svg></p>'
);
};

exports.escapeAngleBracketsInDivAttr = function() {
var document = domino.createDocument(
`<div>You don't have JS! Click<a href="#" title="Search for </div><script>alert(1)</script> without JS">here</a> to go to the no-js website.</div>`
);
// Ensure that HTML entities are properly encoded inside <style>
document.body.innerHTML.should.equal(
`<div>You don't have JS! Click<a href="#" title="Search for &lt;/div&gt;&lt;script&gt;alert(1)&lt;/script&gt; without JS">here</a> to go to the no-js website.</div>`
);
};

0 comments on commit 9eea7b4

Please sign in to comment.