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

Commit

Permalink
fix(security): do not auto-bootstrap when loaded from an extension.
Browse files Browse the repository at this point in the history
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 a7076dc commit 0ff10e1
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,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
Expand Down Expand Up @@ -1602,6 +1622,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);
}
Expand Down
2 changes: 2 additions & 0 deletions test/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@
"getBlockNodes": false,
"createMap": false,
"VALIDITY_STATE_PROPERTY": true,
"allowAutoBootstrap": false,
"isAutoBootstrapAllowed": false,

/* AngularPublic.js */
"version": false,
Expand Down
21 changes: 21 additions & 0 deletions test/AngularSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1682,6 +1682,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;
});
});


Expand Down

0 comments on commit 0ff10e1

Please sign in to comment.