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

Commit e403d80

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 e403d80

File tree

2 files changed

+96
-28
lines changed

2 files changed

+96
-28
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/sanitizeSpec.js

+36-21
Original file line numberDiff line numberDiff line change
@@ -226,42 +226,57 @@ describe('HTML', function() {
226226
expectHTML(false).toBe('false');
227227
});
228228

229-
it('should accept SVG tags', function() {
229+
it('should strip svg elements if not enabled via provider', 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('');
232+
});
233+
234+
235+
describe('SVG support', function() {
236+
237+
beforeEach(module(function($sanitizeProvider) {
238+
$sanitizeProvider.enableSvg(true);
239+
}));
240+
241+
242+
it('should accept SVG tags', function() {
230243
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>')
231244
.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>');
232-
});
245+
});
233246

234-
it('should not ignore white-listed svg camelCased attributes', function() {
235-
expectHTML('<svg preserveAspectRatio="true"></svg>')
247+
it('should not ignore white-listed svg camelCased attributes', function() {
248+
expectHTML('<svg preserveAspectRatio="true"></svg>')
236249
.toEqual('<svg preserveAspectRatio="true"></svg>');
237250

238-
});
251+
});
239252

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>')
253+
it('should sanitize SVG xlink:href attribute values', function() {
254+
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:href="javascript:alert()"></a></svg>')
242255
.toEqual('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>');
243256

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>')
257+
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>')
245258
.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-
});
259+
});
247260

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>');
261+
it('should sanitize unknown namespaced SVG attributes', function() {
262+
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:foo="javascript:alert()"></a></svg>')
263+
.toEqual('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>');
251264

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

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

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>');
273+
expectHTML('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle>' +
274+
'<animate attributeName="xlink:href" begin="0" from="javascript:alert(1)" to="&" /></a></svg>')
275+
.toEqual('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle></a></svg>');
276+
});
263277
});
264278

279+
265280
describe('htmlSanitizerWriter', function() {
266281
/* global htmlSanitizeWriter: false */
267282
if (angular.isUndefined(window.htmlSanitizeWriter)) return;

0 commit comments

Comments
 (0)