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

Commit ca88480

Browse files
committed
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.
1 parent 3b7f29f commit ca88480

File tree

3 files changed

+51
-0
lines changed

3 files changed

+51
-0
lines changed

src/Angular.js

+28
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@
9797
NODE_TYPE_DOCUMENT,
9898
NODE_TYPE_DOCUMENT_FRAGMENT
9999
*/
100+
/* global
101+
console
102+
*/
100103

101104
////////////////////////////////////
102105

@@ -1444,6 +1447,26 @@ function getNgAttribute(element, ngAttr) {
14441447
return null;
14451448
}
14461449

1450+
function allowAutoBootstrap(document) {
1451+
if (!document.currentScript) {
1452+
return true;
1453+
}
1454+
var src = document.currentScript.getAttribute('src');
1455+
var link = document.createElement('a');
1456+
link.href = src;
1457+
var scriptProtocol = link.protocol;
1458+
var docLoadProtocol = document.location.protocol;
1459+
if ((scriptProtocol === 'resource:' ||
1460+
scriptProtocol === 'chrome-extension:') &&
1461+
docLoadProtocol !== scriptProtocol) {
1462+
return false;
1463+
}
1464+
return true;
1465+
}
1466+
1467+
// Cached as it has to run during loading so that document.currentScript is available.
1468+
var isAutoBootstrapAllowed = allowAutoBootstrap(window.document);
1469+
14471470
/**
14481471
* @ngdoc directive
14491472
* @name ngApp
@@ -1602,6 +1625,11 @@ function angularInit(element, bootstrap) {
16021625
}
16031626
});
16041627
if (appElement) {
1628+
if (!isAutoBootstrapAllowed) {
1629+
console.error('Angular: disabling automatic bootstrap. <script> protocol indicates an ' +
1630+
'extension, document.location.href does not match.');
1631+
return;
1632+
}
16051633
config.strictDi = getNgAttribute(appElement, 'strict-di') !== null;
16061634
bootstrap(appElement, module ? [module] : [], config);
16071635
}

test/.eslintrc.json

+2
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@
104104
"getBlockNodes": false,
105105
"createMap": false,
106106
"VALIDITY_STATE_PROPERTY": true,
107+
"allowAutoBootstrap": false,
108+
"isAutoBootstrapAllowed": false,
107109

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

test/AngularSpec.js

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

16831683
dealoc(appElement);
16841684
});
1685+
1686+
it('should not bootstrap from an extension into a non-extension document', function() {
1687+
var src = 'resource://something';
1688+
// Fake a minimal document object (the actual document.currentScript is readonly).
1689+
var fakeDoc = {
1690+
currentScript: { getAttribute: function() { return src; } },
1691+
location: {protocol: 'http:'},
1692+
createElement: document.createElement.bind(document)
1693+
};
1694+
expect(allowAutoBootstrap(fakeDoc)).toBe(false);
1695+
1696+
src = 'file://whatever';
1697+
expect(allowAutoBootstrap(fakeDoc)).toBe(true);
1698+
});
1699+
1700+
it('should not bootstrap if bootstrapping is disabled', function() {
1701+
isAutoBootstrapAllowed = false;
1702+
angularInit(jqLite('<div ng-app></div>')[0], bootstrapSpy);
1703+
expect(bootstrapSpy).not.toHaveBeenCalled();
1704+
isAutoBootstrapAllowed = true;
1705+
});
16851706
});
16861707

16871708

0 commit comments

Comments
 (0)