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

Commit

Permalink
feat($sanitize): make svg support an opt-in
Browse files Browse the repository at this point in the history
Closes #12524

BREAKING CHANGE: The svg support in  is now an opt-in option

Applications that depend on this option can use  to turn the option back on,
but while doing so, please read the warning provided in the  documentation for
information on preventing click-hijacking attacks when this option is turned on.
  • Loading branch information
IgorMinar authored and petebacondarwin committed Sep 18, 2015
1 parent 94207f8 commit 181fc56
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 35 deletions.
71 changes: 64 additions & 7 deletions src/ngSanitize/sanitize.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
* @kind function
*
* @description
* Sanitizes an html string by stripping all potentially dangerous tokens.
*
* The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are
* then serialized back to properly escaped html string. This means that no unsafe input can make
* it into the returned string, however, since our parser is more strict than a typical browser
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
* browser, won't make it through the sanitizer. The input may also contain SVG markup.
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
* it into the returned string.
*
* The whitelist for URL sanitization of attribute values is configured using the functions
* `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider
* `$compileProvider`}.
*
* The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}.
*
* @param {string} html HTML input.
* @returns {string} Sanitized HTML.
Expand Down Expand Up @@ -126,8 +130,22 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
</file>
</example>
*/


/**
* @ngdoc provider
* @name $sanitizeProvider
*
* @description
* Creates and configures {@link $sanitize} instance.
*/
function $SanitizeProvider() {
var svgEnabled = false;

this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
if (svgEnabled) {
angular.extend(validElements, svgElements);
}
return function(html) {
var buf = [];
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
Expand All @@ -136,6 +154,46 @@ function $SanitizeProvider() {
return buf.join('');
};
}];


/**
* @ngdoc method
* @name $sanitizeProvider#enableSvg
* @kind function
*
* @description
* Enables a subset of svg to be supported by the sanitizer.
*
* <div class="alert alert-warning">
* <p>By enabling this setting without taking other precautions, you might expose your
* application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned
* outside of the containing element and be rendered over other elements on the page (e.g. a login
* link). Such behavior can then result in phishing incidents.</p>
*
* <p>To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg
* tags within the sanitized content:</p>
*
* <br>
*
* <pre><code>
* .rootOfTheIncludedContent svg {
* overflow: hidden !important;
* }
* </code></pre>
* </div>
*
* @param {boolean=} regexp New regexp to whitelist urls with.
* @returns {boolean|ng.$sanitizeProvider} Returns the currently configured value if called
* without an argument or self for chaining otherwise.
*/
this.enableSvg = function(enableSvg) {
if (angular.isDefined(enableSvg)) {
svgEnabled = enableSvg;
return this;
} else {
return svgEnabled;
}
};
}

function sanitizeText(chars) {
Expand Down Expand Up @@ -193,8 +251,7 @@ var validElements = angular.extend({},
voidElements,
blockElements,
inlineElements,
optionalEndTagElements,
svgElements);
optionalEndTagElements);

//Attributes that have href and hence need to be sanitized
var uriAttrs = toMap("background,cite,href,longdesc,src,usemap,xlink:href");
Expand Down
6 changes: 4 additions & 2 deletions test/ngSanitize/filter/linkySpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ describe('linky', function() {

it('should handle target:', function() {
expect(linky("http://example.com", "_blank")).
toEqual('<a target="_blank" href="http://example.com">http://example.com</a>');
toBeOneOf('<a target="_blank" href="http://example.com">http://example.com</a>',
'<a href="http://example.com" target="_blank">http://example.com</a>');
expect(linky("http://example.com", "someNamedIFrame")).
toEqual('<a target="someNamedIFrame" href="http://example.com">http://example.com</a>');
toBeOneOf('<a target="someNamedIFrame" href="http://example.com">http://example.com</a>',
'<a href="http://example.com" target="someNamedIFrame">http://example.com</a>');
});
});
76 changes: 50 additions & 26 deletions test/ngSanitize/sanitizeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ describe('HTML', function() {
// THESE TESTS ARE EXECUTED WITH COMPILED ANGULAR
it('should echo html', function() {
expectHTML('hello<b class="1\'23" align=\'""\'>world</b>.').
toEqual('hello<b class="1\'23" align="&#34;&#34;">world</b>.');
toBeOneOf('hello<b class="1\'23" align="&#34;&#34;">world</b>.',
'hello<b align="&#34;&#34;" class="1\'23">world</b>.');
});

it('should remove script', function() {
Expand Down Expand Up @@ -180,7 +181,8 @@ describe('HTML', function() {

it('should ignore back slash as escape', function() {
expectHTML('<img alt="xxx\\" title="><script>....">').
toEqual('<img alt="xxx\\" title="&gt;&lt;script&gt;....">');
toBeOneOf('<img alt="xxx\\" title="&gt;&lt;script&gt;....">',
'<img title="&gt;&lt;script&gt;...." alt="xxx\\">');
});

it('should ignore object attributes', function() {
Expand Down Expand Up @@ -214,42 +216,64 @@ describe('HTML', function() {
expectHTML(false).toBe('false');
});

it('should accept SVG tags', function() {
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>')
.toEqual('<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"></circle></svg>');
it('should strip svg elements if not enabled via provider', function() {
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>')
.toEqual('');
});

it('should not ignore white-listed svg camelCased attributes', function() {
expectHTML('<svg preserveAspectRatio="true"></svg>')

describe('SVG support', function() {

beforeEach(module(function($sanitizeProvider) {
$sanitizeProvider.enableSvg(true);
}));


it('should accept SVG tags', function() {
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>')
.toBeOneOf('<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"></circle></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" height="150px" width="400px"><circle fill="red" stroke-width="3" stroke="black" r="40" cy="50" cx="50"></circle></svg>',
'<svg width="400px" height="150px" xmlns="http://www.w3.org/2000/svg"><circle fill="red" stroke="black" stroke-width="3" cx="50" cy="50" r="40"></circle></svg>');
});

it('should not ignore white-listed svg camelCased attributes', function() {
expectHTML('<svg preserveAspectRatio="true"></svg>')
.toEqual('<svg preserveAspectRatio="true"></svg>');

});
});

it('should sanitize SVG xlink:href attribute values', function() {
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:href="javascript:alert()"></a></svg>')
.toEqual('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>');
it('should sanitize SVG xlink:href attribute values', function() {
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:href="javascript:alert()"></a></svg>')
.toBeOneOf('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>',
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a></a></svg>');

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>')
.toEqual('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:href="https://example.com"></a></svg>');
});
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>')
.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>',
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a xlink:href="https://example.com"></a></svg>');
});

it('should sanitize unknown namespaced SVG attributes', function() {
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:foo="javascript:alert()"></a></svg>')
.toEqual('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>');
it('should sanitize unknown namespaced SVG attributes', function() {
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:foo="javascript:alert()"></a></svg>')
.toBeOneOf('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>',
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a></a></svg>');

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>')
.toEqual('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>');
});
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>')
.toBeOneOf('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>',
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a></a></svg>');
});

it('should not accept SVG animation tags', function() {
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>')
.toEqual('<svg xmlns:xlink="http://www.w3.org/1999/xlink"><a><text y="1em">Click me</text></a></svg>');
it('should not accept SVG animation tags', function() {
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>')
.toEqual('<svg xmlns:xlink="http://www.w3.org/1999/xlink"><a><text y="1em">Click me</text></a></svg>');

expectHTML('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle>' +
'<animate attributeName="xlink:href" begin="0" from="javascript:alert(1)" to="&" /></a></svg>')
.toEqual('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle></a></svg>');
expectHTML('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle>' +
'<animate attributeName="xlink:href" begin="0" from="javascript:alert(1)" to="&" /></a></svg>')
.toBeOneOf('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle></a></svg>',
'<svg><a xlink:href="?" xmlns:xlink="http://www.w3.org/1999/xlink"><circle r="400"></circle></a></svg>');
});
});


describe('htmlSanitizerWriter', function() {
/* global htmlSanitizeWriter: false */
if (angular.isUndefined(window.htmlSanitizeWriter)) return;
Expand Down

0 comments on commit 181fc56

Please sign in to comment.