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

Commit e7ef92e

Browse files
committed
feat($sanitize): make svg support an opt-in
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.
1 parent bcb9d64 commit e7ef92e

File tree

3 files changed

+113
-35
lines changed

3 files changed

+113
-35
lines changed

src/ngSanitize/sanitize.js

+60-7
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,17 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
3434
* @kind function
3535
*
3636
* @description
37+
* Sanitizes an html string by stripping all potentially dangerous tokens.
38+
*
3739
* The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are
3840
* then serialized back to properly escaped html string. This means that no unsafe input can make
39-
* it into the returned string, however, since our parser is more strict than a typical browser
40-
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
41-
* browser, won't make it through the sanitizer. The input may also contain SVG markup.
42-
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
43-
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
41+
* it into the returned string.
42+
*
43+
* The whitelist for URL sanitization of attribute values is configured using the functions
44+
* `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider
45+
* `$compileProvider`}.
46+
*
47+
* The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}.
4448
*
4549
* @param {string} html HTML input.
4650
* @returns {string} Sanitized HTML.
@@ -126,8 +130,22 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
126130
</file>
127131
</example>
128132
*/
133+
134+
135+
/**
136+
* @ngdoc provider
137+
* @name $sanitizeProvider
138+
*
139+
* @description
140+
* Creates and configures {@link $sanitize} instance.
141+
*/
129142
function $SanitizeProvider() {
143+
var svgEnabled = false;
144+
130145
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
146+
if (svgEnabled) {
147+
angular.extend(validElements, svgElements);
148+
}
131149
return function(html) {
132150
var buf = [];
133151
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
@@ -136,6 +154,42 @@ function $SanitizeProvider() {
136154
return buf.join('');
137155
};
138156
}];
157+
158+
159+
/**
160+
* @ngdoc method
161+
* @name $sanitizeProvider#enableSvg
162+
* @kind function
163+
*
164+
* @description
165+
* Enables a subset of svg to be supported by the sanitizer.
166+
*
167+
* **Warning**: By enabling this setting without taking other precautions, you might expose your
168+
* application to click-hijacking attacks. In these attacks, a sanitize svg could be positioned
169+
* outside of the containing element and be rendered over other elements on the page (e.g. a login
170+
* link). Such behavior can then result in phishing incidents.
171+
*
172+
* To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg
173+
* tags within the sanitized content:
174+
*
175+
* ```
176+
* .rootOfTheIncludedContent svg {
177+
* overflow: hidden !important;
178+
* }
179+
* ```
180+
*
181+
* @param {boolean=} regexp New regexp to whitelist urls with.
182+
* @returns {boolean|ng.$sanitizeProvider} Returns the currently configured value if called
183+
* without a argument or self for chaining otherwise.
184+
*/
185+
this.enableSvg = function(enableSvg) {
186+
if (isDefined(enableSvg)) {
187+
svgEnabled = enableSvg;
188+
return this;
189+
} else {
190+
return svgEnabled;
191+
}
192+
}
139193
}
140194

141195
function sanitizeText(chars) {
@@ -193,8 +247,7 @@ var validElements = angular.extend({},
193247
voidElements,
194248
blockElements,
195249
inlineElements,
196-
optionalEndTagElements,
197-
svgElements);
250+
optionalEndTagElements);
198251

199252
//Attributes that have href and hence need to be sanitized
200253
var uriAttrs = toMap("background,cite,href,longdesc,src,usemap,xlink:href");

test/ngSanitize/filter/linkySpec.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ describe('linky', function() {
5050

5151
it('should handle target:', function() {
5252
expect(linky("http://example.com", "_blank")).
53-
toEqual('<a target="_blank" href="http://example.com">http://example.com</a>');
53+
toBeOneOf('<a target="_blank" href="http://example.com">http://example.com</a>',
54+
'<a href="http://example.com" target="_blank">http://example.com</a>');
5455
expect(linky("http://example.com", "someNamedIFrame")).
55-
toEqual('<a target="someNamedIFrame" href="http://example.com">http://example.com</a>');
56+
toBeOneOf('<a target="someNamedIFrame" href="http://example.com">http://example.com</a>',
57+
'<a href="http://example.com" target="someNamedIFrame">http://example.com</a>');
5658
});
5759
});

test/ngSanitize/sanitizeSpec.js

+49-26
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ describe('HTML', function() {
112112
// THESE TESTS ARE EXECUTED WITH COMPILED ANGULAR
113113
it('should echo html', function() {
114114
expectHTML('hello<b class="1\'23" align=\'""\'>world</b>.').
115-
toEqual('hello<b class="1\'23" align="&#34;&#34;">world</b>.');
115+
toBeOneOf('hello<b class="1\'23" align="&#34;&#34;">world</b>.',
116+
'hello<b align="&#34;&#34;" class="1\'23">world</b>.');
116117
});
117118

118119
it('should remove script', function() {
@@ -192,7 +193,8 @@ describe('HTML', function() {
192193

193194
it('should ignore back slash as escape', function() {
194195
expectHTML('<img alt="xxx\\" title="><script>....">').
195-
toEqual('<img alt="xxx\\" title="&gt;&lt;script&gt;....">');
196+
toBeOneOf('<img alt="xxx\\" title="&gt;&lt;script&gt;....">',
197+
'<img title="&gt;&lt;script&gt;...." alt="xxx\\">');
196198
});
197199

198200
it('should ignore object attributes', function() {
@@ -226,42 +228,63 @@ describe('HTML', function() {
226228
expectHTML(false).toBe('false');
227229
});
228230

229-
it('should accept SVG tags', function() {
230-
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>')
231-
.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>');
231+
it('should strip svg elements if not enabled via provider', function() {
232+
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>')
233+
.toEqual('');
232234
});
233235

234-
it('should not ignore white-listed svg camelCased attributes', function() {
235-
expectHTML('<svg preserveAspectRatio="true"></svg>')
236+
237+
describe('SVG support', function() {
238+
239+
beforeEach(module(function($sanitizeProvider) {
240+
$sanitizeProvider.enableSvg(true);
241+
}));
242+
243+
244+
it('should accept SVG tags', function() {
245+
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>')
246+
.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>',
247+
'<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>');
248+
});
249+
250+
it('should not ignore white-listed svg camelCased attributes', function() {
251+
expectHTML('<svg preserveAspectRatio="true"></svg>')
236252
.toEqual('<svg preserveAspectRatio="true"></svg>');
237253

238-
});
254+
});
239255

240-
it('should sanitize SVG xlink:href attribute values', function() {
241-
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:href="javascript:alert()"></a></svg>')
242-
.toEqual('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>');
256+
it('should sanitize SVG xlink:href attribute values', function() {
257+
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:href="javascript:alert()"></a></svg>')
258+
.toBeOneOf('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>',
259+
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a></a></svg>');
243260

244-
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>')
245-
.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>');
246-
});
261+
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>')
262+
.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>',
263+
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a xlink:href="https://example.com"></a></svg>');
264+
});
247265

248-
it('should sanitize unknown namespaced SVG attributes', function() {
249-
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:foo="javascript:alert()"></a></svg>')
250-
.toEqual('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>');
266+
it('should sanitize unknown namespaced SVG attributes', function() {
267+
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:foo="javascript:alert()"></a></svg>')
268+
.toBeOneOf('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>',
269+
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a></a></svg>');
251270

252-
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>')
253-
.toEqual('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>');
254-
});
271+
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>')
272+
.toBeOneOf('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>',
273+
'<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><a></a></svg>');
274+
});
255275

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

260-
expectHTML('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle>' +
261-
'<animate attributeName="xlink:href" begin="0" from="javascript:alert(1)" to="&" /></a></svg>')
262-
.toEqual('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle></a></svg>');
280+
expectHTML('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle>' +
281+
'<animate attributeName="xlink:href" begin="0" from="javascript:alert(1)" to="&" /></a></svg>')
282+
.toBeOneOf('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle></a></svg>',
283+
'<svg><a xlink:href="?" xmlns:xlink="http://www.w3.org/1999/xlink"><circle r="400"></circle></a></svg>');
284+
});
263285
});
264286

287+
265288
describe('htmlSanitizerWriter', function() {
266289
/* global htmlSanitizeWriter: false */
267290
if (angular.isUndefined(window.htmlSanitizeWriter)) return;

0 commit comments

Comments
 (0)