Skip to content
Permalink
Browse files

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
  • Loading branch information...
mprobst authored and petebacondarwin committed Nov 2, 2016
1 parent 6a24885 commit 6ce2913d99bb0dade6027ba9733295d0aa13b242
Showing with 48 additions and 0 deletions.
  1. +25 −0 src/Angular.js
  2. +2 −0 test/.eslintrc.json
  3. +21 −0 test/AngularSpec.js
@@ -1439,6 +1439,26 @@ function getNgAttribute(element, ngAttr) {
return null;
}

function allowAutoBootstrap(document) {
if (!document.currentScript) {
return true;
}
var src = document.currentScript.getAttribute('src');
var link = document.createElement('a');
link.href = src;
var scriptProtocol = link.protocol;
var docLoadProtocol = document.location.protocol;
if ((scriptProtocol === 'resource:' ||
scriptProtocol === 'chrome-extension:') &&
docLoadProtocol !== scriptProtocol) {
return false;
}
return true;
}

// Cached as it has to run during loading so that document.currentScript is available.
var isAutoBootstrapAllowed = allowAutoBootstrap(window.document);

/**
* @ngdoc directive
* @name ngApp
@@ -1597,6 +1617,11 @@ function angularInit(element, bootstrap) {
}
});
if (appElement) {
if (!isAutoBootstrapAllowed) {
window.console.error('Angular: disabling automatic bootstrap. <script> protocol indicates ' +
'an extension, document.location.href does not match.');
return;
}
config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
bootstrap(appElement, module ? [module] : [], config);
}
@@ -103,6 +103,8 @@
"getBlockNodes": false,
"createMap": false,
"VALIDITY_STATE_PROPERTY": true,
"allowAutoBootstrap": false,
"isAutoBootstrapAllowed": false,

/* AngularPublic.js */
"version": false,
@@ -1699,6 +1699,27 @@ describe('angular', function() {

dealoc(appElement);
});

it('should not bootstrap from an extension into a non-extension document', function() {
var src = 'resource://something';
// Fake a minimal document object (the actual document.currentScript is readonly).
var fakeDoc = {
currentScript: { getAttribute: function() { return src; } },
location: {protocol: 'http:'},
createElement: document.createElement.bind(document)
};
expect(allowAutoBootstrap(fakeDoc)).toBe(false);

src = 'file://whatever';
expect(allowAutoBootstrap(fakeDoc)).toBe(true);
});

it('should not bootstrap if bootstrapping is disabled', function() {
isAutoBootstrapAllowed = false;
angularInit(jqLite('<div ng-app></div>')[0], bootstrapSpy);
expect(bootstrapSpy).not.toHaveBeenCalled();
isAutoBootstrapAllowed = true;
});
});


0 comments on commit 6ce2913

Please sign in to comment.
You can’t perform that action at this time.