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

Commit 181fc56

Browse files
IgorMinarpetebacondarwin
authored andcommitted
feat($sanitize): make svg support an opt-in
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.
1 parent 94207f8 commit 181fc56

File tree

3 files changed

+118
-35
lines changed

3 files changed

+118
-35
lines changed

src/ngSanitize/sanitize.js

+64-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,46 @@ 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+
* <div class="alert alert-warning">
168+
* <p>By enabling this setting without taking other precautions, you might expose your
169+
* application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned
170+
* outside of the containing element and be rendered over other elements on the page (e.g. a login
171+
* link). Such behavior can then result in phishing incidents.</p>
172+
*
173+
* <p>To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg
174+
* tags within the sanitized content:</p>
175+
*
176+
* <br>
177+
*
178+
* <pre><code>
179+
* .rootOfTheIncludedContent svg {
180+
* overflow: hidden !important;
181+
* }
182+
* </code></pre>
183+
* </div>
184+
*
185+
* @param {boolean=} regexp New regexp to whitelist urls with.
186+
* @returns {boolean|ng.$sanitizeProvider} Returns the currently configured value if called
187+
* without an argument or self for chaining otherwise.
188+
*/
189+
this.enableSvg = function(enableSvg) {
190+
if (angular.isDefined(enableSvg)) {
191+
svgEnabled = enableSvg;
192+
return this;
193+
} else {
194+
return svgEnabled;
195+
}
196+
};
139197
}
140198

141199
function sanitizeText(chars) {
@@ -193,8 +251,7 @@ var validElements = angular.extend({},
193251
voidElements,
194252
blockElements,
195253
inlineElements,
196-
optionalEndTagElements,
197-
svgElements);
254+
optionalEndTagElements);
198255

199256
//Attributes that have href and hence need to be sanitized
200257
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

+50-26
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ describe('HTML', function() {
100100
// THESE TESTS ARE EXECUTED WITH COMPILED ANGULAR
101101
it('should echo html', function() {
102102
expectHTML('hello<b class="1\'23" align=\'""\'>world</b>.').
103-
toEqual('hello<b class="1\'23" align="&#34;&#34;">world</b>.');
103+
toBeOneOf('hello<b class="1\'23" align="&#34;&#34;">world</b>.',
104+
'hello<b align="&#34;&#34;" class="1\'23">world</b>.');
104105
});
105106

106107
it('should remove script', function() {
@@ -180,7 +181,8 @@ describe('HTML', function() {
180181

181182
it('should ignore back slash as escape', function() {
182183
expectHTML('<img alt="xxx\\" title="><script>....">').
183-
toEqual('<img alt="xxx\\" title="&gt;&lt;script&gt;....">');
184+
toBeOneOf('<img alt="xxx\\" title="&gt;&lt;script&gt;....">',
185+
'<img title="&gt;&lt;script&gt;...." alt="xxx\\">');
184186
});
185187

186188
it('should ignore object attributes', function() {
@@ -214,42 +216,64 @@ describe('HTML', function() {
214216
expectHTML(false).toBe('false');
215217
});
216218

217-
it('should accept SVG tags', function() {
218-
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>')
219-
.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>');
219+
it('should strip svg elements if not enabled via provider', function() {
220+
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>')
221+
.toEqual('');
220222
});
221223

222-
it('should not ignore white-listed svg camelCased attributes', function() {
223-
expectHTML('<svg preserveAspectRatio="true"></svg>')
224+
225+
describe('SVG support', function() {
226+
227+
beforeEach(module(function($sanitizeProvider) {
228+
$sanitizeProvider.enableSvg(true);
229+
}));
230+
231+
232+
it('should accept SVG tags', function() {
233+
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>')
234+
.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>',
235+
'<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>',
236+
'<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>');
237+
});
238+
239+
it('should not ignore white-listed svg camelCased attributes', function() {
240+
expectHTML('<svg preserveAspectRatio="true"></svg>')
224241
.toEqual('<svg preserveAspectRatio="true"></svg>');
225242

226-
});
243+
});
227244

228-
it('should sanitize SVG xlink:href attribute values', function() {
229-
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:href="javascript:alert()"></a></svg>')
230-
.toEqual('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>');
245+
it('should sanitize SVG xlink:href attribute values', function() {
246+
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:href="javascript:alert()"></a></svg>')
247+
.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>');
231249

232-
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>')
233-
.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>');
234-
});
250+
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>')
251+
.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>');
253+
});
235254

236-
it('should sanitize unknown namespaced SVG attributes', function() {
237-
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:foo="javascript:alert()"></a></svg>')
238-
.toEqual('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>');
255+
it('should sanitize unknown namespaced SVG attributes', function() {
256+
expectHTML('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a xlink:foo="javascript:alert()"></a></svg>')
257+
.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>');
239259

240-
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>')
241-
.toEqual('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><a></a></svg>');
242-
});
260+
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>')
261+
.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>');
263+
});
243264

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

248-
expectHTML('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle>' +
249-
'<animate attributeName="xlink:href" begin="0" from="javascript:alert(1)" to="&" /></a></svg>')
250-
.toEqual('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle></a></svg>');
269+
expectHTML('<svg><a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="?"><circle r="400"></circle>' +
270+
'<animate attributeName="xlink:href" begin="0" from="javascript:alert(1)" to="&" /></a></svg>')
271+
.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>');
273+
});
251274
});
252275

276+
253277
describe('htmlSanitizerWriter', function() {
254278
/* global htmlSanitizeWriter: false */
255279
if (angular.isUndefined(window.htmlSanitizeWriter)) return;

0 commit comments

Comments
 (0)