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

fix(sce/urlUtils): Use baseURI for same-origin checks, and bump base#href to RESOURCE_URL #15537

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -3256,6 +3256,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// maction[xlink:href] can source SVG. It's not limited to <maction>.
} else if (attrNormalizedName === 'xlinkHref' ||
(tag === 'form' && attrNormalizedName === 'action') ||
// base href is just plain weird and scary. If relative URLs don't go where they are
// expected to, all bets are off.
(tag === 'base' && attrNormalizedName === 'href') ||
// links can be stylesheets or imports, which can run script in the current origin
(tag === 'link' && attrNormalizedName === 'href')
) {
Expand Down
1 change: 1 addition & 0 deletions src/ng/sce.js
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ function $SceDelegateProvider() {
}

function isResourceUrlAllowedByPolicy(url) {
// The urlResolve step should take care of baseURI if it's set.
var parsedUrl = urlResolve(url.toString());
var i, n, allowed = false;
// Ensure that at least one item from the whitelist allows this url.
Expand Down
14 changes: 11 additions & 3 deletions src/ng/urlUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
// exactly the behavior needed here. There is little value is mocking these out for this
// service.
var urlParsingNode = window.document.createElement('a');
var originUrl = urlResolve(window.location.href);

// We use the baseURI instead of the location. The behavior of relative URIs is more intuitive
// this way when mixed with a base tag, and the $sce security checks will rely on this. Also note
// that baseURI can evolve over time, so we need to reevaluate.
var getOriginUrl = function() {
return urlResolve(window.document.baseURI);
};


/**
Expand Down Expand Up @@ -85,13 +91,15 @@ function urlResolve(url) {

/**
* Parse a request URL and determine whether this is a same-origin request as the application document.
* We rely on document.baseURI to determine our origin, to make applications that use base behave as
* expected with relative URIs.
*
* @param {string|object} requestUrl The url of the request as a string that will be resolved
* or a parsed URL object.
* @returns {boolean} Whether the request is for the same origin as the application document.
*/
function urlIsSameOrigin(requestUrl) {
var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl;
return (parsed.protocol === originUrl.protocol &&
parsed.host === originUrl.host);
return (parsed.protocol === getOriginUrl().protocol &&
parsed.host === getOriginUrl().host);
}
16 changes: 16 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11162,6 +11162,22 @@ describe('$compile', function() {
}));
});

describe('base[href]', function() {
it('should be a RESOURCE_URL context', inject(function($compile, $rootScope, $sce) {
element = $compile('<base href="{{testUrl}}"/>')($rootScope);

$rootScope.testUrl = $sce.trustAsResourceUrl('https://example.com/');
$rootScope.$apply();
expect(element.attr('href')).toContain('https://example.com/');

$rootScope.testUrl = 'https://not.example.com/';
expect(function() { $rootScope.$apply(); }).toThrowMinErr(
'$interpolate', 'interr', 'Can\'t interpolate: {{testUrl}}\nError: [$sce:insecurl] Blocked ' +
'loading resource from url not allowed by $sceDelegate policy. URL: ' +
'https://not.example.com/');
}));
});

// Support: IE 9-10 only
// IEs <11 don't support srcdoc
if (!msie || msie === 11) {
Expand Down
25 changes: 21 additions & 4 deletions test/ng/urlUtilsSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ describe('urlUtils', function() {
});

describe('isSameOrigin', function() {

function expectIsSameOrigin(url, expectedValue) {
expect(urlIsSameOrigin(url)).toBe(expectedValue);
expect(urlIsSameOrigin(urlResolve(url))).toBe(expectedValue);
}

it('should support various combinations of urls - both string and parsed', inject(function($document) {
function expectIsSameOrigin(url, expectedValue) {
expect(urlIsSameOrigin(url)).toBe(expectedValue);
expect(urlIsSameOrigin(urlResolve(url))).toBe(expectedValue);
}
expectIsSameOrigin('path', true);
var origin = urlResolve($document[0].location.href);
expectIsSameOrigin('//' + origin.host + '/path', true);
Expand All @@ -40,5 +42,20 @@ describe('urlUtils', function() {
// This assumes that the test is *not* running on port 22 (very unlikely).
expectIsSameOrigin('//' + origin.hostname + ':22/path', false);
}));


it('should follow document.baseURI', inject(function($document) {
$document[0].head.appendChild($document[0].createElement('base'));
$document[0].head.lastChild.href = 'http://example.com/';
expectIsSameOrigin('path', true);
var origin = urlResolve($document[0].location.href);

// Real origin shouldn't be considered okay anymore.
expectIsSameOrigin('//' + origin.host + '/path', false);

// But the baseURI should.
expectIsSameOrigin('http://example.com/path', true);
$document[0].head.lastChild.href = null;
}));
});
});