diff --git a/src/ng/compile.js b/src/ng/compile.js index b33e830a42c0..7d2b6dc7a97c 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -153,7 +153,8 @@ function $CompileProvider($provide) { Suffix = 'Directive', COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/, - urlSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/; + aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|file):/, + imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//; // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes // The assumption is that future DOM event attribute names will begin with @@ -213,32 +214,61 @@ function $CompileProvider($provide) { /** * @ngdoc function - * @name ng.$compileProvider#urlSanitizationWhitelist + * @name ng.$compileProvider#aHrefSanitizationWhitelist * @methodOf ng.$compileProvider * @function * * @description * Retrieves or overrides the default regular expression that is used for whitelisting of safe - * urls during a[href] and img[src] sanitization. + * urls during a[href] sanitization. * * The sanitization is a security measure aimed at prevent XSS attacks via html links. * - * Any url about to be assigned to a[href] or img[src] via data-binding is first normalized and - * turned into an absolute url. Afterwards, the url is matched against the - * `urlSanitizationWhitelist` regular expression. If a match is found, the original url is written - * into the dom. Otherwise, the absolute url is prefixed with `'unsafe:'` string and only then is - * it written into the DOM. + * Any url about to be assigned to a[href] via data-binding is first normalized and turned into + * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` + * regular expression. If a match is found, the original url is written into the dom. Otherwise, + * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. * * @param {RegExp=} regexp New regexp to whitelist urls with. * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for * chaining otherwise. */ - this.urlSanitizationWhitelist = function(regexp) { + this.aHrefSanitizationWhitelist = function(regexp) { if (isDefined(regexp)) { - urlSanitizationWhitelist = regexp; + aHrefSanitizationWhitelist = regexp; return this; } - return urlSanitizationWhitelist; + return aHrefSanitizationWhitelist; + }; + + + /** + * @ngdoc function + * @name ng.$compileProvider#imgSrcSanitizationWhitelist + * @methodOf ng.$compileProvider + * @function + * + * @description + * Retrieves or overrides the default regular expression that is used for whitelisting of safe + * urls during img[src] sanitization. + * + * The sanitization is a security measure aimed at prevent XSS attacks via html links. + * + * Any url about to be assigned to img[src] via data-binding is first normalized and turned into an + * absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` regular + * expression. If a match is found, the original url is written into the dom. Otherwise, the + * absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. + * + * @param {RegExp=} regexp New regexp to whitelist urls with. + * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for + * chaining otherwise. + */ + this.imgSrcSanitizationWhitelist = function(regexp) { + if (isDefined(regexp)) { + imgSrcSanitizationWhitelist = regexp; + return this; + } + return imgSrcSanitizationWhitelist; }; @@ -298,8 +328,11 @@ function $CompileProvider($provide) { // href property always returns normalized absolute url, so we can match against that normalizedVal = urlSanitizationNode.href; - if (normalizedVal !== '' && !normalizedVal.match(urlSanitizationWhitelist)) { - this[key] = value = 'unsafe:' + normalizedVal; + if (normalizedVal !== '') { + if ((key === 'href' && !normalizedVal.match(aHrefSanitizationWhitelist)) || + (key === 'src' && !normalizedVal.match(imgSrcSanitizationWhitelist))) { + this[key] = value = 'unsafe:' + normalizedVal; + } } } diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index e0c68301261d..97a58c10f7be 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -2551,15 +2551,38 @@ describe('$compile', function() { expect(element.attr('src')).toBe('unsafe:javascript:doEvilStuff()'); })); - it('should sanitize data: urls', inject(function($compile, $rootScope) { + it('should sanitize non-image data: urls', inject(function($compile, $rootScope) { element = $compile('')($rootScope); - $rootScope.testUrl = "data:evilPayload"; + $rootScope.testUrl = "data:application/javascript;charset=US-ASCII,alert('evil!');"; + $rootScope.$apply(); + expect(element.attr('src')).toBe("unsafe:data:application/javascript;charset=US-ASCII,alert('evil!');"); + $rootScope.testUrl = "data:,foo"; $rootScope.$apply(); + expect(element.attr('src')).toBe("unsafe:data:,foo"); + })); + + + it('should not sanitize data: URIs for images', inject(function($compile, $rootScope) { + element = $compile('')($rootScope); - expect(element.attr('src')).toBe('unsafe:data:evilPayload'); + // image data uri + // ref: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever + $rootScope.dataUri = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; + $rootScope.$apply(); + expect(element.attr('src')).toBe('data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='); })); + // Fails on IE < 10 with "TypeError: Access is denied" when trying to set img[src] + if (!msie || msie > 10) { + it('should sanitize mailto: urls', inject(function($compile, $rootScope) { + element = $compile('')($rootScope); + $rootScope.testUrl = "mailto:foo@bar.com"; + $rootScope.$apply(); + expect(element.attr('src')).toBe('unsafe:mailto:foo@bar.com'); + })); + } + it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) { element = $compile('')($rootScope); @@ -2636,13 +2659,6 @@ describe('$compile', function() { $rootScope.$apply(); expect(element.attr('src')).toBe('ftp://foo.com/bar'); - // Fails on IE < 10 with "TypeError: Access is denied" when trying to set img[src] - if (!msie || msie > 10) { - $rootScope.testUrl = "mailto:foo@bar.com"; - $rootScope.$apply(); - expect(element.attr('src')).toBe('mailto:foo@bar.com'); - } - $rootScope.testUrl = "file:///foo/bar.html"; $rootScope.$apply(); expect(element.attr('src')).toBe('file:///foo/bar.html'); @@ -2660,8 +2676,8 @@ describe('$compile', function() { it('should allow reconfiguration of the src whitelist', function() { module(function($compileProvider) { - expect($compileProvider.urlSanitizationWhitelist() instanceof RegExp).toBe(true); - var returnVal = $compileProvider.urlSanitizationWhitelist(/javascript:/); + expect($compileProvider.imgSrcSanitizationWhitelist() instanceof RegExp).toBe(true); + var returnVal = $compileProvider.imgSrcSanitizationWhitelist(/javascript:/); expect(returnVal).toBe($compileProvider); }); @@ -2812,8 +2828,8 @@ describe('$compile', function() { it('should allow reconfiguration of the href whitelist', function() { module(function($compileProvider) { - expect($compileProvider.urlSanitizationWhitelist() instanceof RegExp).toBe(true); - var returnVal = $compileProvider.urlSanitizationWhitelist(/javascript:/); + expect($compileProvider.aHrefSanitizationWhitelist() instanceof RegExp).toBe(true); + var returnVal = $compileProvider.aHrefSanitizationWhitelist(/javascript:/); expect(returnVal).toBe($compileProvider); });