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

Commit 6ccbfa6

Browse files
rjametgkalpak
authored andcommitted
feat($compile): lower the xlink:href security context for SVG's a and image elements
Previously, `xlink:href` on SVG's `<a>` and `<image>` elements, was `$sce.RESOURCE_URL`. While this makes sense for other `xlink:href` usecases, it was an overkill for these elements. This commit lowers the `xlink:href` security context for these specific elements, treating it in the same way as `a[href]` or `img[src]` respectively. The `xlink:href` security context for other elements is not affected. BREAKING CHANGE: In the unlikely case that an app relied on RESOURCE_URL whitelisting for the purpose of binding to the `xlink:href` property of SVG's `<a>` or `<image>` elements and if the values do not pass the regular URL sanitization, they will break. To fix this you need to ensure that the values used for binding to the affected `xlink:href` contexts are considered safe URLs, e.g. by whitelisting them in `$compileProvider`'s `aHrefSanitizationWhitelist` (for `<a>` elements) or `imgSrcSanitizationWhitelist` (for `<image>` elements). Closes #15736
1 parent cc793a1 commit 6ccbfa6

File tree

2 files changed

+46
-8
lines changed

2 files changed

+46
-8
lines changed

src/ng/compile.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -1673,9 +1673,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
16731673
nodeName = nodeName_(this.$$element);
16741674

16751675
if ((nodeName === 'a' && (key === 'href' || key === 'xlinkHref')) ||
1676-
(nodeName === 'img' && key === 'src')) {
1676+
(nodeName === 'img' && key === 'src') ||
1677+
(nodeName === 'image' && key === 'xlinkHref')) {
16771678
// sanitize a[href] and img[src] values
1678-
this[key] = value = $$sanitizeUri(value, key === 'src');
1679+
this[key] = value = $$sanitizeUri(value, nodeName === 'img' || nodeName === 'image');
16791680
} else if (nodeName === 'img' && key === 'srcset' && isDefined(value)) {
16801681
// sanitize img[srcset] values
16811682
var result = '';
@@ -3256,8 +3257,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
32563257
if (['img', 'video', 'audio', 'source', 'track'].indexOf(tag) === -1) {
32573258
return $sce.RESOURCE_URL;
32583259
}
3259-
// maction[xlink:href] can source SVG. It's not limited to <maction>.
3260-
} else if (attrNormalizedName === 'xlinkHref' ||
3260+
} else if (
3261+
// Some xlink:href are okay, most aren't
3262+
(attrNormalizedName === 'xlinkHref' && (tag !== 'image' && tag !== 'a')) ||
3263+
// Formaction
32613264
(tag === 'form' && attrNormalizedName === 'action') ||
32623265
// If relative URLs can go where they are not expected to, then
32633266
// all sorts of trust issues can arise.

test/ng/compileSpec.js

+39-4
Original file line numberDiff line numberDiff line change
@@ -11122,23 +11122,47 @@ describe('$compile', function() {
1112211122
});
1112311123

1112411124
it('should use $$sanitizeUri when working with svg and xlink:href', function() {
11125+
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
11126+
module(function($provide) {
11127+
$provide.value('$$sanitizeUri', $$sanitizeUri);
11128+
});
11129+
inject(function($compile, $rootScope) {
11130+
var elementA = $compile('<svg><a xlink:href="{{ testUrl + \'aTag\' }}"></a></svg>')($rootScope);
11131+
var elementImage = $compile('<svg><image xlink:href="{{ testUrl + \'imageTag\' }}"></image></svg>')($rootScope);
11132+
11133+
//both of these fail the RESOURCE_URL test, that shouldn't be run
11134+
$rootScope.testUrl = 'https://bad.example.org';
11135+
$$sanitizeUri.and.returnValue('https://clean.example.org');
11136+
11137+
$rootScope.$apply();
11138+
expect(elementA.find('a').attr('xlink:href')).toBe('https://clean.example.org');
11139+
expect(elementImage.find('image').attr('xlink:href')).toBe('https://clean.example.org');
11140+
// <a> is navigational, so the second argument should be false to reach the aHref whitelist
11141+
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl + 'aTag' , false);
11142+
// <image> is media inclusion, it should use the imgSrc whitelist
11143+
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl + 'imageTag', true);
11144+
});
11145+
});
11146+
11147+
it('should use $$sanitizeUri when working with svg and xlink:href through ng-href', function() {
1112511148
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
1112611149
module(function($provide) {
1112711150
$provide.value('$$sanitizeUri', $$sanitizeUri);
1112811151
});
1112911152
inject(function($compile, $rootScope) {
1113011153
element = $compile('<svg><a xlink:href="" ng-href="{{ testUrl }}"></a></svg>')($rootScope);
11131-
$rootScope.testUrl = 'evilUrl';
11154+
//both of these fail the RESOURCE_URL test, that shouldn't be run
11155+
$rootScope.testUrl = 'https://bad.example.org';
11156+
$$sanitizeUri.and.returnValue('https://clean.example.org');
1113211157

11133-
$$sanitizeUri.and.returnValue('someSanitizedUrl');
1113411158
$rootScope.$apply();
11135-
expect(element.find('a').prop('href').baseVal).toBe('someSanitizedUrl');
11159+
expect(element.find('a').prop('href').baseVal).toBe('https://clean.example.org');
1113611160
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false);
1113711161
});
1113811162
});
1113911163

1114011164

11141-
it('should use $$sanitizeUri when working with svg and xlink:href', function() {
11165+
it('should use $$sanitizeUri when working with svg and xlink:href through ng-href', function() {
1114211166
var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri');
1114311167
module(function($provide) {
1114411168
$provide.value('$$sanitizeUri', $$sanitizeUri);
@@ -11153,6 +11177,17 @@ describe('$compile', function() {
1115311177
expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false);
1115411178
});
1115511179
});
11180+
11181+
it('should have a RESOURCE_URL context for xlink:href by default', function() {
11182+
inject(function($compile, $rootScope) {
11183+
element = $compile('<svg><whatever xlink:href="{{ testUrl }}"></whatever></svg>')($rootScope);
11184+
$rootScope.testUrl = 'https://bad.example.org';
11185+
11186+
expect(function() {
11187+
$rootScope.$apply();
11188+
}).toThrowError(/\$sce:insecurl/);
11189+
});
11190+
});
1115611191
});
1115711192

1115811193
describe('interpolation on HTML DOM event handler attributes onclick, onXYZ, formaction', function() {

0 commit comments

Comments
 (0)