From 1adf29af13890d61286840177607edd552a9df97 Mon Sep 17 00:00:00 2001
From: Chirayu Krishnappa
Date: Fri, 21 Jun 2013 12:33:03 -0700
Subject: fix($compile): sanitize values bound to img[src]
Ref: 9532234bf1c408af9a6fd2c4743fdb585b920531
BREAKING CHANGE: img[src] URLs are now sanitized using the same whitelist
    as a[href] URLs.  The most obvious impact is if you were using data:
    URIs.  data: URIs will be whitelisted for img[src] in a future
    commit.
---
 test/ng/compileSpec.js | 151 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 150 insertions(+), 1 deletion(-)
(limited to 'test')
diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js
index 8b48ea08..b713476b 100755
--- a/test/ng/compileSpec.js
+++ b/test/ng/compileSpec.js
@@ -2536,7 +2536,156 @@ describe('$compile', function() {
   });
 
 
-  describe('href sanitization', function() {
+  describe('img[src] sanitization', function() {
+    it('should NOT require trusted values for img src', inject(function($rootScope, $compile) {
+      element = $compile(' ')($rootScope);
+      $rootScope.testUrl = 'http://example.com/image.png';
+      $rootScope.$digest();
+      expect(element.attr('src')).toEqual('http://example.com/image.png');
+    }));
+
+    it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
+      element = $compile('
')($rootScope);
+      $rootScope.testUrl = 'http://example.com/image.png';
+      $rootScope.$digest();
+      expect(element.attr('src')).toEqual('http://example.com/image.png');
+    }));
+
+    it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
+      element = $compile(' ')($rootScope);
+      $rootScope.testUrl = "javascript:doEvilStuff()";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('unsafe:javascript:doEvilStuff()');
+    }));
+
+    it('should sanitize data: urls', inject(function($compile, $rootScope) {
+      element = $compile('
')($rootScope);
+      $rootScope.testUrl = "javascript:doEvilStuff()";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('unsafe:javascript:doEvilStuff()');
+    }));
+
+    it('should sanitize data: urls', inject(function($compile, $rootScope) {
+      element = $compile(' ')($rootScope);
+      $rootScope.testUrl = "data:evilPayload";
+      $rootScope.$apply();
+
+      expect(element.attr('src')).toBe('unsafe:data:evilPayload');
+    }));
+
+
+    it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) {
+      element = $compile('
')($rootScope);
+      $rootScope.testUrl = "data:evilPayload";
+      $rootScope.$apply();
+
+      expect(element.attr('src')).toBe('unsafe:data:evilPayload');
+    }));
+
+
+    it('should sanitize obfuscated javascript: urls', inject(function($compile, $rootScope) {
+      element = $compile(' ')($rootScope);
+
+      // case-sensitive
+      $rootScope.testUrl = "JaVaScRiPt:doEvilStuff()";
+      $rootScope.$apply();
+      expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
+
+      // tab in protocol
+      $rootScope.testUrl = "java\u0009script:doEvilStuff()";
+      $rootScope.$apply();
+      expect(element[0].src).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
+
+      // space before
+      $rootScope.testUrl = " javascript:doEvilStuff()";
+      $rootScope.$apply();
+      expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
+
+      // ws chars before
+      $rootScope.testUrl = " \u000e javascript:doEvilStuff()";
+      $rootScope.$apply();
+      expect(element[0].src).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
+
+      // post-fixed with proper url
+      $rootScope.testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
+      $rootScope.$apply();
+      expect(element[0].src).toBeOneOf(
+          'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
+          'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
+      );
+    }));
+
+    it('should sanitize ng-src bindings as well', inject(function($compile, $rootScope) {
+      element = $compile('
')($rootScope);
+
+      // case-sensitive
+      $rootScope.testUrl = "JaVaScRiPt:doEvilStuff()";
+      $rootScope.$apply();
+      expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
+
+      // tab in protocol
+      $rootScope.testUrl = "java\u0009script:doEvilStuff()";
+      $rootScope.$apply();
+      expect(element[0].src).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
+
+      // space before
+      $rootScope.testUrl = " javascript:doEvilStuff()";
+      $rootScope.$apply();
+      expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
+
+      // ws chars before
+      $rootScope.testUrl = " \u000e javascript:doEvilStuff()";
+      $rootScope.$apply();
+      expect(element[0].src).toMatch(/(http:\/\/|unsafe:javascript:doEvilStuff\(\))/);
+
+      // post-fixed with proper url
+      $rootScope.testUrl = "javascript:doEvilStuff(); http://make.me/look/good";
+      $rootScope.$apply();
+      expect(element[0].src).toBeOneOf(
+          'unsafe:javascript:doEvilStuff(); http://make.me/look/good',
+          'unsafe:javascript:doEvilStuff();%20http://make.me/look/good'
+      );
+    }));
+
+    it('should sanitize ng-src bindings as well', inject(function($compile, $rootScope) {
+      element = $compile('![]() ')($rootScope);
+      $rootScope.testUrl = "javascript:doEvilStuff()";
+      $rootScope.$apply();
+
+      expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
+    }));
+
+
+    it('should not sanitize valid urls', inject(function($compile, $rootScope) {
+      element = $compile('
')($rootScope);
+      $rootScope.testUrl = "javascript:doEvilStuff()";
+      $rootScope.$apply();
+
+      expect(element[0].src).toBe('unsafe:javascript:doEvilStuff()');
+    }));
+
+
+    it('should not sanitize valid urls', inject(function($compile, $rootScope) {
+      element = $compile(' ')($rootScope);
+
+      $rootScope.testUrl = "foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('foo/bar');
+
+      $rootScope.testUrl = "/foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('/foo/bar');
+
+      $rootScope.testUrl = "../foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('../foo/bar');
+
+      $rootScope.testUrl = "#foo";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('#foo');
+
+      $rootScope.testUrl = "http://foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('http://foo/bar');
+
+      $rootScope.testUrl = " http://foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe(' http://foo/bar');
+
+      $rootScope.testUrl = "https://foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('https://foo/bar');
+
+      $rootScope.testUrl = "ftp://foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('ftp://foo/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');
+    }));
+
+
+    it('should not sanitize attributes other than src', inject(function($compile, $rootScope) {
+      element = $compile('
')($rootScope);
+
+      $rootScope.testUrl = "foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('foo/bar');
+
+      $rootScope.testUrl = "/foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('/foo/bar');
+
+      $rootScope.testUrl = "../foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('../foo/bar');
+
+      $rootScope.testUrl = "#foo";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('#foo');
+
+      $rootScope.testUrl = "http://foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('http://foo/bar');
+
+      $rootScope.testUrl = " http://foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe(' http://foo/bar');
+
+      $rootScope.testUrl = "https://foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('https://foo/bar');
+
+      $rootScope.testUrl = "ftp://foo/bar";
+      $rootScope.$apply();
+      expect(element.attr('src')).toBe('ftp://foo/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');
+    }));
+
+
+    it('should not sanitize attributes other than src', inject(function($compile, $rootScope) {
+      element = $compile('![{{testUrl}}]() ')($rootScope);
+      $rootScope.testUrl = "javascript:doEvilStuff()";
+      $rootScope.$apply();
+
+      expect(element.attr('title')).toBe('javascript:doEvilStuff()');
+    }));
+
+
+    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(returnVal).toBe($compileProvider);
+      });
+
+      inject(function($compile, $rootScope) {
+        element = $compile('
')($rootScope);
+      $rootScope.testUrl = "javascript:doEvilStuff()";
+      $rootScope.$apply();
+
+      expect(element.attr('title')).toBe('javascript:doEvilStuff()');
+    }));
+
+
+    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(returnVal).toBe($compileProvider);
+      });
+
+      inject(function($compile, $rootScope) {
+        element = $compile(' ')($rootScope);
+
+        // Fails on IE < 10 with "TypeError: Object doesn't support this property or method" when
+        // trying to set img[src]
+        if (!msie || msie > 10) {
+          $rootScope.testUrl = "javascript:doEvilStuff()";
+          $rootScope.$apply();
+          expect(element.attr('src')).toBe('javascript:doEvilStuff()');
+        }
+
+        $rootScope.testUrl = "http://recon/figured";
+        $rootScope.$apply();
+        expect(element.attr('src')).toBe('unsafe:http://recon/figured');
+      });
+    });
+
+  });
+
+
+  describe('a[href] sanitization', function() {
 
     it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
       element = $compile('')($rootScope);
-- 
cgit v1.2.3
')($rootScope);
+
+        // Fails on IE < 10 with "TypeError: Object doesn't support this property or method" when
+        // trying to set img[src]
+        if (!msie || msie > 10) {
+          $rootScope.testUrl = "javascript:doEvilStuff()";
+          $rootScope.$apply();
+          expect(element.attr('src')).toBe('javascript:doEvilStuff()');
+        }
+
+        $rootScope.testUrl = "http://recon/figured";
+        $rootScope.$apply();
+        expect(element.attr('src')).toBe('unsafe:http://recon/figured');
+      });
+    });
+
+  });
+
+
+  describe('a[href] sanitization', function() {
 
     it('should sanitize javascript: urls', inject(function($compile, $rootScope) {
       element = $compile('')($rootScope);
-- 
cgit v1.2.3