Skip to content

Commit 3e39ac7

Browse files
committed
fix($compile): allow data: image URIs in img[src]
Ref: 1adf29a BREAKING CHANGE: img[src] URLs are now sanitized via a separate whitelist regex instead of sharing the whitelist regex with a[href]. With this change, img[src] URLs may also be data: URI's matching mime types image/*. mailto: URLs are disallowed (and do not make sense for img[src] but were allowed under the a[href] whitelist used before.)
1 parent e449c6d commit 3e39ac7

File tree

2 files changed

+76
-27
lines changed

2 files changed

+76
-27
lines changed

src/ng/compile.js

+46-13
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,8 @@ function $CompileProvider($provide) {
153153
Suffix = 'Directive',
154154
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
155155
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
156-
urlSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/;
156+
aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/,
157+
imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//;
157158

158159
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
159160
// The assumption is that future DOM event attribute names will begin with
@@ -213,32 +214,61 @@ function $CompileProvider($provide) {
213214

214215
/**
215216
* @ngdoc function
216-
* @name ng.$compileProvider#urlSanitizationWhitelist
217+
* @name ng.$compileProvider#aHrefSanitizationWhitelist
217218
* @methodOf ng.$compileProvider
218219
* @function
219220
*
220221
* @description
221222
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
222-
* urls during a[href] and img[src] sanitization.
223+
* urls during a[href] sanitization.
223224
*
224225
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
225226
*
226-
* Any url about to be assigned to a[href] or img[src] via data-binding is first normalized and
227-
* turned into an absolute url. Afterwards, the url is matched against the
228-
* `urlSanitizationWhitelist` regular expression. If a match is found, the original url is written
229-
* into the dom. Otherwise, the absolute url is prefixed with `'unsafe:'` string and only then is
230-
* it written into the DOM.
227+
* Any url about to be assigned to a[href] via data-binding is first normalized and turned into
228+
* an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist`
229+
* regular expression. If a match is found, the original url is written into the dom. Otherwise,
230+
* the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
231231
*
232232
* @param {RegExp=} regexp New regexp to whitelist urls with.
233233
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
234234
* chaining otherwise.
235235
*/
236-
this.urlSanitizationWhitelist = function(regexp) {
236+
this.aHrefSanitizationWhitelist = function(regexp) {
237237
if (isDefined(regexp)) {
238-
urlSanitizationWhitelist = regexp;
238+
aHrefSanitizationWhitelist = regexp;
239239
return this;
240240
}
241-
return urlSanitizationWhitelist;
241+
return aHrefSanitizationWhitelist;
242+
};
243+
244+
245+
/**
246+
* @ngdoc function
247+
* @name ng.$compileProvider#imgSrcSanitizationWhitelist
248+
* @methodOf ng.$compileProvider
249+
* @function
250+
*
251+
* @description
252+
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
253+
* urls during img[src] sanitization.
254+
*
255+
* The sanitization is a security measure aimed at prevent XSS attacks via html links.
256+
*
257+
* Any url about to be assigned to img[src] via data-binding is first normalized and turned into an
258+
* absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` regular
259+
* expression. If a match is found, the original url is written into the dom. Otherwise, the
260+
* absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM.
261+
*
262+
* @param {RegExp=} regexp New regexp to whitelist urls with.
263+
* @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for
264+
* chaining otherwise.
265+
*/
266+
this.imgSrcSanitizationWhitelist = function(regexp) {
267+
if (isDefined(regexp)) {
268+
imgSrcSanitizationWhitelist = regexp;
269+
return this;
270+
}
271+
return imgSrcSanitizationWhitelist;
242272
};
243273

244274

@@ -298,8 +328,11 @@ function $CompileProvider($provide) {
298328

299329
// href property always returns normalized absolute url, so we can match against that
300330
normalizedVal = urlSanitizationNode.href;
301-
if (normalizedVal !== '' && !normalizedVal.match(urlSanitizationWhitelist)) {
302-
this[key] = value = 'unsafe:' + normalizedVal;
331+
if (normalizedVal !== '') {
332+
if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) ||
333+
(key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) {
334+
this[key] = value = 'unsafe:' + normalizedVal;
335+
}
303336
}
304337
}
305338

test/ng/compileSpec.js

+30-14
Original file line numberDiff line numberDiff line change
@@ -2551,15 +2551,38 @@ describe('$compile', function() {
25512551
expect(element.attr('src')).toBe('unsafe:javascript:doEvilStuff()');
25522552
}));
25532553

2554-
it('should sanitize data: urls', inject(function($compile, $rootScope) {
2554+
it('should sanitize non-image data: urls', inject(function($compile, $rootScope) {
25552555
element = $compile('<img src="{{testUrl}}"></a>')($rootScope);
2556-
$rootScope.testUrl = "data:evilPayload";
2556+
$rootScope.testUrl = "data:application/javascript;charset=US-ASCII,alert('evil!');";
2557+
$rootScope.$apply();
2558+
expect(element.attr('src')).toBe("unsafe:data:application/javascript;charset=US-ASCII,alert('evil!');");
2559+
$rootScope.testUrl = "data:,foo";
25572560
$rootScope.$apply();
2561+
expect(element.attr('src')).toBe("unsafe:data:,foo");
2562+
}));
2563+
2564+
2565+
it('should not sanitize data: URIs for images', inject(function($compile, $rootScope) {
2566+
element = $compile('<img src="{{dataUri}}"></img>')($rootScope);
25582567

2559-
expect(element.attr('src')).toBe('unsafe:data:evilPayload');
2568+
// image data uri
2569+
// ref: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
2570+
$rootScope.dataUri = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
2571+
$rootScope.$apply();
2572+
expect(element.attr('src')).toBe('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
25602573
}));
25612574

25622575

2576+
// Fails on IE < 10 with "TypeError: Access is denied" when trying to set img[src]
2577+
if (!msie || msie > 10) {
2578+
it('should sanitize mailto: urls', inject(function($compile, $rootScope) {
2579+
element = $compile('<img src="{{testUrl}}"></a>')($rootScope);
2580+
$rootScope.testUrl = "mailto:[email protected]";
2581+
$rootScope.$apply();
2582+
expect(element.attr('src')).toBe('unsafe:mailto:[email protected]');
2583+
}));
2584+
}
2585+
25632586
it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) {
25642587
element = $compile('<img src="{{testUrl}}"></img>')($rootScope);
25652588

@@ -2636,13 +2659,6 @@ describe('$compile', function() {
26362659
$rootScope.$apply();
26372660
expect(element.attr('src')).toBe('ftp://foo.com/bar');
26382661

2639-
// Fails on IE < 10 with "TypeError: Access is denied" when trying to set img[src]
2640-
if (!msie || msie > 10) {
2641-
$rootScope.testUrl = "mailto:[email protected]";
2642-
$rootScope.$apply();
2643-
expect(element.attr('src')).toBe('mailto:[email protected]');
2644-
}
2645-
26462662
$rootScope.testUrl = "file:///foo/bar.html";
26472663
$rootScope.$apply();
26482664
expect(element.attr('src')).toBe('file:///foo/bar.html');
@@ -2660,8 +2676,8 @@ describe('$compile', function() {
26602676

26612677
it('should allow reconfiguration of the src whitelist', function() {
26622678
module(function($compileProvider) {
2663-
expect($compileProvider.urlSanitizationWhitelist() instanceof RegExp).toBe(true);
2664-
var returnVal = $compileProvider.urlSanitizationWhitelist(/javascript:/);
2679+
expect($compileProvider.imgSrcSanitizationWhitelist() instanceof RegExp).toBe(true);
2680+
var returnVal = $compileProvider.imgSrcSanitizationWhitelist(/javascript:/);
26652681
expect(returnVal).toBe($compileProvider);
26662682
});
26672683

@@ -2812,8 +2828,8 @@ describe('$compile', function() {
28122828

28132829
it('should allow reconfiguration of the href whitelist', function() {
28142830
module(function($compileProvider) {
2815-
expect($compileProvider.urlSanitizationWhitelist() instanceof RegExp).toBe(true);
2816-
var returnVal = $compileProvider.urlSanitizationWhitelist(/javascript:/);
2831+
expect($compileProvider.aHrefSanitizationWhitelist() instanceof RegExp).toBe(true);
2832+
var returnVal = $compileProvider.aHrefSanitizationWhitelist(/javascript:/);
28172833
expect(returnVal).toBe($compileProvider);
28182834
});
28192835

0 commit comments

Comments
 (0)