diff options
| author | Chirayu Krishnappa | 2013-07-16 12:48:29 -0700 | 
|---|---|---|
| committer | Chirayu Krishnappa | 2013-07-18 11:29:50 -0700 | 
| commit | 3e39ac7e1b10d4812a44dad2f959a93361cd823b (patch) | |
| tree | 96185a21871d78862c63e8c1adf16cf18d0f66c7 | |
| parent | e449c6df06d92136f9fab95caa29ac2e74b5e58b (diff) | |
| download | angular.js-3e39ac7e1b10d4812a44dad2f959a93361cd823b.tar.bz2 | |
fix($compile): allow data: image URIs in img[src]
Ref: 1adf29af13890d61286840177607edd552a9df97
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.)
| -rw-r--r-- | src/ng/compile.js | 59 | ||||
| -rwxr-xr-x | test/ng/compileSpec.js | 44 | 
2 files changed, 76 insertions, 27 deletions
| diff --git a/src/ng/compile.js b/src/ng/compile.js index b33e830a..7d2b6dc7 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 e0c68301..97a58c10 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('<img src="{{testUrl}}"></a>')($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('<img src="{{dataUri}}"></img>')($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('<img src="{{testUrl}}"></a>')($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('<img src="{{testUrl}}"></img>')($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);        }); | 
