Skip to content

Commit 8b369dd

Browse files
mprobstpetebacondarwin
authored andcommitted
fix(security): do not auto-bootstrap when loaded from an extension.
Extension URIs (`resource://...`) bypass Content-Security-Policy in Chrome and Firefox and can always be loaded. Now if a site already has a XSS bug, and uses CSP to protect itself, but the user has an extension installed that uses Angular, an attacked can load Angular from the extension, and Angular's auto-bootstrapping can be used to bypass the victim site's CSP protection. Notes: - `isAutoBootstrapAllowed` must be initialized on load, so that `currentScript` is set correctly. - The tests are a bit indirect as reproducing the actual scenario is too complicated to reproduce (requires signing an extension etc). I have confirmed this to be working manually. Closes angular#15346
1 parent 4c5966d commit 8b369dd

File tree

3 files changed

+48
-0
lines changed

3 files changed

+48
-0
lines changed

src/Angular.js

+25
Original file line numberDiff line numberDiff line change
@@ -1439,6 +1439,26 @@ function getNgAttribute(element, ngAttr) {
14391439
return null;
14401440
}
14411441

1442+
function allowAutoBootstrap(document) {
1443+
if (!document.currentScript) {
1444+
return true;
1445+
}
1446+
var src = document.currentScript.getAttribute('src');
1447+
var link = document.createElement('a');
1448+
link.href = src;
1449+
var scriptProtocol = link.protocol;
1450+
var docLoadProtocol = document.location.protocol;
1451+
if ((scriptProtocol === 'resource:' ||
1452+
scriptProtocol === 'chrome-extension:') &&
1453+
docLoadProtocol !== scriptProtocol) {
1454+
return false;
1455+
}
1456+
return true;
1457+
}
1458+
1459+
// Cached as it has to run during loading so that document.currentScript is available.
1460+
var isAutoBootstrapAllowed = allowAutoBootstrap(window.document);
1461+
14421462
/**
14431463
* @ngdoc directive
14441464
* @name ngApp
@@ -1597,6 +1617,11 @@ function angularInit(element, bootstrap) {
15971617
}
15981618
});
15991619
if (appElement) {
1620+
if (!isAutoBootstrapAllowed) {
1621+
window.console.error('Angular: disabling automatic bootstrap. <script> protocol indicates ' +
1622+
'an extension, document.location.href does not match.');
1623+
return;
1624+
}
16001625
config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
16011626
bootstrap(appElement, module ? [module] : [], config);
16021627
}

test/.eslintrc.json

+2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@
103103
"getBlockNodes": false,
104104
"createMap": false,
105105
"VALIDITY_STATE_PROPERTY": true,
106+
"allowAutoBootstrap": false,
107+
"isAutoBootstrapAllowed": false,
106108

107109
/* AngularPublic.js */
108110
"version": false,

test/AngularSpec.js

+21
Original file line numberDiff line numberDiff line change
@@ -1699,6 +1699,27 @@ describe('angular', function() {
16991699

17001700
dealoc(appElement);
17011701
});
1702+
1703+
it('should not bootstrap from an extension into a non-extension document', function() {
1704+
var src = 'resource://something';
1705+
// Fake a minimal document object (the actual document.currentScript is readonly).
1706+
var fakeDoc = {
1707+
currentScript: { getAttribute: function() { return src; } },
1708+
location: {protocol: 'http:'},
1709+
createElement: document.createElement.bind(document)
1710+
};
1711+
expect(allowAutoBootstrap(fakeDoc)).toBe(false);
1712+
1713+
src = 'file://whatever';
1714+
expect(allowAutoBootstrap(fakeDoc)).toBe(true);
1715+
});
1716+
1717+
it('should not bootstrap if bootstrapping is disabled', function() {
1718+
isAutoBootstrapAllowed = false;
1719+
angularInit(jqLite('<div ng-app></div>')[0], bootstrapSpy);
1720+
expect(bootstrapSpy).not.toHaveBeenCalled();
1721+
isAutoBootstrapAllowed = true;
1722+
});
17021723
});
17031724

17041725

0 commit comments

Comments
 (0)