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

Commit fccaffc

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 5a2af15 commit fccaffc

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

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

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
@@ -1696,6 +1696,27 @@ describe('angular', function() {
16961696

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

17011722

0 commit comments

Comments
 (0)