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

Commit 0ff10e1

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 #15346
1 parent a7076dc commit 0ff10e1

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
@@ -1444,6 +1444,26 @@ function getNgAttribute(element, ngAttr) {
14441444
return null;
14451445
}
14461446

1447+
function allowAutoBootstrap(document) {
1448+
if (!document.currentScript) {
1449+
return true;
1450+
}
1451+
var src = document.currentScript.getAttribute('src');
1452+
var link = document.createElement('a');
1453+
link.href = src;
1454+
var scriptProtocol = link.protocol;
1455+
var docLoadProtocol = document.location.protocol;
1456+
if ((scriptProtocol === 'resource:' ||
1457+
scriptProtocol === 'chrome-extension:') &&
1458+
docLoadProtocol !== scriptProtocol) {
1459+
return false;
1460+
}
1461+
return true;
1462+
}
1463+
1464+
// Cached as it has to run during loading so that document.currentScript is available.
1465+
var isAutoBootstrapAllowed = allowAutoBootstrap(window.document);
1466+
14471467
/**
14481468
* @ngdoc directive
14491469
* @name ngApp
@@ -1602,6 +1622,11 @@ function angularInit(element, bootstrap) {
16021622
}
16031623
});
16041624
if (appElement) {
1625+
if (!isAutoBootstrapAllowed) {
1626+
window.console.error('Angular: disabling automatic bootstrap. <script> protocol indicates ' +
1627+
'an extension, document.location.href does not match.');
1628+
return;
1629+
}
16051630
config.strictDi = getNgAttribute(appElement, 'strict-di') !== null;
16061631
bootstrap(appElement, module ? [module] : [], config);
16071632
}

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)