Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit bc0d8c4

Browse files
IgorMinarpetebacondarwin
authored andcommitted
fix($sanitize): add mXSS protection
Closes #12524
1 parent 9b84fca commit bc0d8c4

File tree

3 files changed

+87
-7
lines changed

3 files changed

+87
-7
lines changed
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@ngdoc error
2+
@name $sanitize:uinput
3+
@fullName Failed to sanitize html because the input is unstable
4+
@description
5+
6+
This error occurs when `$sanitize` sanitizer tries to check the input for possible mXSS payload and the verification
7+
errors due to the input mutating indefinitely. This could be a sign that the payload contains code exploiting an mXSS
8+
vulnerability in the browser.
9+
10+
mXSS attack exploit browser bugs that cause some browsers parse a certain html strings into DOM, which once serialized
11+
doesn't match the original input. These browser bugs can be exploited by attackers to create payload which looks
12+
harmless to sanitizers, but due to mutations caused by the browser are turned into dangerous code once processed after
13+
sanitization.

src/ngSanitize/sanitize.js

+51
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,23 @@ function htmlParser(html, handler) {
334334
html = '' + html;
335335
}
336336
inertBodyElement.innerHTML = html;
337+
338+
//mXSS protection
339+
var mXSSAttempts = 5;
340+
do {
341+
if (mXSSAttempts === 0) {
342+
throw $sanitizeMinErr('uinput', "Failed to sanitize html because the input is unstable");
343+
}
344+
mXSSAttempts--;
345+
346+
// strip custom-namespaced attributes on IE<=11
347+
if (document.documentMode <= 11) {
348+
stripCustomNsAttrs(inertBodyElement);
349+
}
350+
html = inertBodyElement.innerHTML; //trigger mXSS
351+
inertBodyElement.innerHTML = html;
352+
} while (html !== inertBodyElement.innerHTML);
353+
337354
var node = inertBodyElement.firstChild;
338355
while (node) {
339356
switch (node.nodeType) {
@@ -459,5 +476,39 @@ function htmlSanitizeWriter(buf, uriValidator) {
459476
}
460477

461478

479+
/**
480+
* When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare
481+
* ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want
482+
* to allow any of these custom attributes. This method strips them all.
483+
*
484+
* @param node Root element to process
485+
*/
486+
function stripCustomNsAttrs(node) {
487+
if (node.nodeType === Node.ELEMENT_NODE) {
488+
var attrs = node.attributes;
489+
for (var i = 0, l = attrs.length; i < l; i++) {
490+
var attrNode = attrs[i];
491+
var attrName = attrNode.name.toLowerCase();
492+
if (attrName === 'xmlns:ns1' || attrName.indexOf('ns1:') === 0) {
493+
node.removeAttributeNode(attrNode);
494+
i--;
495+
l--;
496+
}
497+
}
498+
}
499+
500+
var nextNode = node.firstChild;
501+
if (nextNode) {
502+
stripCustomNsAttrs(nextNode);
503+
}
504+
505+
nextNode = node.nextSibling;
506+
if (nextNode) {
507+
stripCustomNsAttrs(nextNode);
508+
}
509+
}
510+
511+
512+
462513
// define ngSanitize module and register $sanitize service
463514
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);

test/ngSanitize/sanitizeSpec.js

+23-7
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,18 @@ describe('HTML', function() {
216216
expectHTML(false).toBe('false');
217217
});
218218

219+
219220
it('should strip svg elements if not enabled via provider', function() {
220221
expectHTML('<svg width="400px" height="150px" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red"></svg>')
221222
.toEqual('');
222223
});
223224

225+
if (/Chrome/.test(window.navigator.userAgent)) {
226+
it('should prevent mXSS attacks', function() {
227+
expectHTML('<a href="&#x3000;javascript:alert(1)">CLICKME</a>').toBe('<a>CLICKME</a>');
228+
});
229+
}
230+
224231

225232
describe('SVG support', function() {
226233

@@ -238,38 +245,47 @@ describe('HTML', function() {
238245

239246
it('should not ignore white-listed svg camelCased attributes', function() {
240247
expectHTML('<svg preserveAspectRatio="true"></svg>')
241-
.toEqual('<svg preserveAspectRatio="true"></svg>');
248+
.toBeOneOf('<svg preserveAspectRatio="true"></svg>',
249+
'<svg preserveAspectRatio="true" xmlns="http://www.w3.org/2000/svg"></svg>');
242250

243251
});
244252

245253
it('should sanitize SVG xlink:href attribute values', function() {
246254
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:href="javascript:alert()"></a></svg>')
247255
.toBeOneOf('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>',
248-
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a></a></svg>');
256+
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a></a></svg>',
257+
'<svg xmlns="http://www.w3.org/2000/svg"><a xmlns:xlink="http://www.w3.org/1999/xlink"></a></svg>');
249258

250259
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:href="https://example.com"></a></svg>')
251260
.toBeOneOf('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:href="https://example.com"></a></svg>',
252-
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a xlink:href="https://example.com"></a></svg>');
261+
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a xlink:href="https://example.com"></a></svg>',
262+
'<svg xmlns="http://www.w3.org/2000/svg"><a xlink:href="https://example.com" xmlns:xlink="http://www.w3.org/1999/xlink"></a></svg>',
263+
'<svg xmlns="http://www.w3.org/2000/svg"><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://example.com"></a></svg>');
253264
});
254265

255266
it('should sanitize unknown namespaced SVG attributes', function() {
256267
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:foo="javascript:alert()"></a></svg>')
257268
.toBeOneOf('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>',
258-
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a></a></svg>');
269+
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a></a></svg>',
270+
'<svg xmlns="http://www.w3.org/2000/svg"><a></a></svg>');
259271

260272
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:bar="https://example.com"></a></svg>')
261273
.toBeOneOf('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>',
262-
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a></a></svg>');
274+
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a></a></svg>',
275+
'<svg xmlns="http://www.w3.org/2000/svg"><a></a></svg>');
263276
});
264277

265278
it('should not accept SVG animation tags', function() {
266279
expectHTML('<svg xmlns:xlink="http://www.w3.org/1999/xlink"><a><text y="1em">Click me</text><animate attributeName="xlink:href" values="javascript:alert(1)"/></a></svg>')
267-
.toEqual('<svg xmlns:xlink="http://www.w3.org/1999/xlink"><a><text y="1em">Click me</text></a></svg>');
280+
.toBeOneOf('<svg xmlns:xlink="http://www.w3.org/1999/xlink"><a><text y="1em">Click me</text></a></svg>',
281+
'<svg xmlns="http://www.w3.org/2000/svg"><a><text y="1em">Click me</text></a></svg>');
268282

269283
expectHTML('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle>' +
270284
'<animate attributeName="xlink:href" begin="0" from="javascript:alert(1)" to="&" /></a></svg>')
271285
.toBeOneOf('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle></a></svg>',
272-
'<svg><a xlink:href="?" xmlns:xlink="http://www.w3.org/1999/xlink"><circle r="400"></circle></a></svg>');
286+
'<svg><a xlink:href="?" xmlns:xlink="http://www.w3.org/1999/xlink"><circle r="400"></circle></a></svg>',
287+
'<svg xmlns="http://www.w3.org/2000/svg"><a xlink:href="?" xmlns:xlink="http://www.w3.org/1999/xlink"><circle r="400"></circle></a></svg>',
288+
'<svg xmlns="http://www.w3.org/2000/svg"><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle></a></svg>');
273289
});
274290
});
275291

0 commit comments

Comments
 (0)