diff --git a/docs/content/error/$parse/isecaf.ngdoc b/docs/content/error/$parse/isecaf.ngdoc
deleted file mode 100644
index 115e1b26d754..000000000000
--- a/docs/content/error/$parse/isecaf.ngdoc
+++ /dev/null
@@ -1,12 +0,0 @@
-@ngdoc error
-@name $parse:isecaf
-@fullName Assigning to Fields of Disallowed Context
-@description
-
-Occurs when an expression attempts to assign a value on a field of any of the `Boolean`, `Number`,
-`String`, `Array`, `Object`, or `Function` constructors or the corresponding prototypes.
-
-Angular bans the modification of these constructors or their prototypes from within expressions,
-since it is a known way to modify the behaviour of existing functions/operations.
-
-To resolve this error, avoid assigning to fields of constructors or their prototypes in expressions.
diff --git a/docs/content/error/$parse/isecdom.ngdoc b/docs/content/error/$parse/isecdom.ngdoc
deleted file mode 100644
index 9f60e189ee92..000000000000
--- a/docs/content/error/$parse/isecdom.ngdoc
+++ /dev/null
@@ -1,47 +0,0 @@
-@ngdoc error
-@name $parse:isecdom
-@fullName Referencing a DOM node in Expression
-@description
-
-Occurs when an expression attempts to access a DOM node.
-
-AngularJS restricts access to DOM nodes from within expressions since it's a known way to
-execute arbitrary Javascript code.
-
-This check is only performed on object index and function calls in Angular expressions. These are
-places that are harder for the developer to guard. Dotted member access (such as a.b.c) does not
-perform this check - it's up to the developer to not expose such sensitive and powerful objects
-directly on the scope chain.
-
-To resolve this error, avoid access to DOM nodes.
-
-
-# Event Handlers and Return Values
-
-The `$parse:isecdom` error also occurs when an event handler invokes a function that returns a DOM
-node.
-
-```html
-
-```
-
-```js
- $scope.iWillReturnDOM = function() {
- return someDomNode;
- }
-```
-
-To fix this issue, avoid returning DOM nodes from event handlers.
-
-*Note: This error often means that you are accessing DOM from your controllers, which is usually
-a sign of poor coding style that violates separation of concerns.*
-
-
-# Implicit Returns in CoffeeScript
-
-This error can occur more frequently when using CoffeeScript, which has a feature called implicit
-returns. This language feature returns the last dereferenced object in the function when the
-function has no explicit return statement.
-
-The solution in this scenario is to add an explicit return statement. For example `return false` to
-the function.
diff --git a/docs/content/error/$parse/isecff.ngdoc b/docs/content/error/$parse/isecff.ngdoc
deleted file mode 100644
index a1e72775d254..000000000000
--- a/docs/content/error/$parse/isecff.ngdoc
+++ /dev/null
@@ -1,17 +0,0 @@
-@ngdoc error
-@name $parse:isecff
-@fullName Referencing 'call', 'apply' and 'bind' Disallowed
-@description
-
-Occurs when an expression attempts to invoke Function's 'call', 'apply' or 'bind'.
-
-Angular bans the invocation of 'call', 'apply' and 'bind' from within expressions
-since access is a known way to modify the behaviour of existing functions.
-
-To resolve this error, avoid using these methods in expressions.
-
-Example expression that would result in this error:
-
-```
-
{{user.sendInfo.call({}, true)}}
-```
diff --git a/docs/content/error/$parse/isecfld.ngdoc b/docs/content/error/$parse/isecfld.ngdoc
deleted file mode 100644
index a19c5fa51e97..000000000000
--- a/docs/content/error/$parse/isecfld.ngdoc
+++ /dev/null
@@ -1,27 +0,0 @@
-@ngdoc error
-@name $parse:isecfld
-@fullName Referencing Disallowed Field in Expression
-@description
-
-Occurs when an expression attempts to access one of the following fields:
-
-* __proto__
-* __defineGetter__
-* __defineSetter__
-* __lookupGetter__
-* __lookupSetter__
-
-AngularJS bans access to these fields from within expressions since
-access is a known way to mess with native objects or
-to execute arbitrary Javascript code.
-
-To resolve this error, avoid using these fields in expressions. As a last resort,
-alias their value and access them through the alias instead.
-
-Example expressions that would result in this error:
-
-```
-
{{user.__proto__.hasOwnProperty = $emit}}
-
-
{{user.__defineGetter__('name', noop)}}
-```
diff --git a/docs/content/error/$parse/isecfn.ngdoc b/docs/content/error/$parse/isecfn.ngdoc
deleted file mode 100644
index 417551cb3606..000000000000
--- a/docs/content/error/$parse/isecfn.ngdoc
+++ /dev/null
@@ -1,10 +0,0 @@
-@ngdoc error
-@name $parse:isecfn
-@fullName Referencing Function Disallowed
-@description
-
-Occurs when an expression attempts to access the 'Function' object (constructor for all functions in JavaScript).
-
-Angular bans access to Function from within expressions since constructor access is a known way to execute arbitrary Javascript code.
-
-To resolve this error, avoid Function access.
diff --git a/docs/content/error/$parse/isecobj.ngdoc b/docs/content/error/$parse/isecobj.ngdoc
deleted file mode 100644
index 8da6e27a3505..000000000000
--- a/docs/content/error/$parse/isecobj.ngdoc
+++ /dev/null
@@ -1,11 +0,0 @@
-@ngdoc error
-@name $parse:isecobj
-@fullName Referencing Object Disallowed
-@description
-
-Occurs when an expression attempts to access the 'Object' object (Root object in JavaScript).
-
-Angular bans access to Object from within expressions since access is a known way to modify
-the behaviour of existing objects.
-
-To resolve this error, avoid Object access.
diff --git a/docs/content/error/$parse/isecwindow.ngdoc b/docs/content/error/$parse/isecwindow.ngdoc
deleted file mode 100644
index e7f4ceeaddd5..000000000000
--- a/docs/content/error/$parse/isecwindow.ngdoc
+++ /dev/null
@@ -1,45 +0,0 @@
-@ngdoc error
-@name $parse:isecwindow
-@fullName Referencing Window object in Expression
-@description
-
-Occurs when an expression attempts to access a Window object.
-
-AngularJS restricts access to the Window object from within expressions since it's a known way to
-execute arbitrary Javascript code.
-
-This check is only performed on object index and function calls in Angular expressions. These are
-places that are harder for the developer to guard. Dotted member access (such as a.b.c) does not
-perform this check - it's up to the developer to not expose such sensitive and powerful objects
-directly on the scope chain.
-
-To resolve this error, avoid Window access.
-
-### Common CoffeeScript Issue
-
-Be aware that if you are using CoffeeScript, it automatically returns the value of the last statement in a
-function. So for instance
-
-```coffeescript
- scope.foo = ->
- window.open 'https://example.com'
-```
-
-compiles to something like
-
-```js
- scope.foo = function() {
- return window.open('https://example.com');
- };
-```
-
-You can see that this function will return the result of calling `window.open`, which is a `Window`
-object.
-
-You can avoid this by explicitly returning something else from the function:
-
-```coffeescript
- scope.foo = ->
- window.open 'https://example.com'
- return true;
-```
diff --git a/docs/content/guide/expression.ngdoc b/docs/content/guide/expression.ngdoc
index 824facd9dfb3..24326b6abae1 100644
--- a/docs/content/guide/expression.ngdoc
+++ b/docs/content/guide/expression.ngdoc
@@ -113,11 +113,11 @@ You can try evaluating different expressions here:
Angular does not use JavaScript's `eval()` to evaluate expressions. Instead Angular's
{@link ng.$parse $parse} service processes these expressions.
-Angular expressions do not have access to global variables like `window`, `document` or `location`.
+Angular expressions do not have direct access to global variables like `window`, `document` or `location`.
This restriction is intentional. It prevents accidental access to the global state – a common source of subtle bugs.
-Instead use services like `$window` and `$location` in functions called from expressions. Such services
-provide mockable access to globals.
+Instead use services like `$window` and `$location` in functions on controllers, which are then called from expressions.
+Such services provide mockable access to globals.
It is possible to access the context object using the identifier `this` and the locals object using the
identifier `$locals`.
diff --git a/docs/content/guide/security.ngdoc b/docs/content/guide/security.ngdoc
index 175c49edf9c0..9fcf0e7c6078 100644
--- a/docs/content/guide/security.ngdoc
+++ b/docs/content/guide/security.ngdoc
@@ -30,42 +30,55 @@ so keeping to AngularJS standards is not just a functionality issue, it's also c
facilitate rapid security updates.
-## Expression Sandboxing
-
-AngularJS's expressions are sandboxed not for security reasons, but instead to maintain a proper
-separation of application responsibilities. For example, access to `window` is disallowed
-because it makes it easy to introduce brittle global state into your application.
-
-However, this sandbox is not intended to stop attackers who can edit the template before it's
-processed by Angular. It may be possible to run arbitrary JavaScript inside double-curly bindings
-if an attacker can modify them.
-
-But if an attacker can change arbitrary HTML templates, there's nothing stopping them from doing:
-
-```html
-
-```
-
-**It's better to design your application in such a way that users cannot change client-side templates.**
-
-For instance:
+## Angular Templates and Expressions
+
+**If an attacker has access to control Angular templates or expressions, they can exploit an Angular application
+via an XSS attack, regardless of the version.**
+
+There are a number of ways that templates and expressions can be controlled:
+
+* **Generating Angular templates on the server containing user-provided content**. This is the most common pitfall
+ where you are generating HTML via some server-side engine such as PHP, Java or ASP.NET.
+* **Passing an expression generated from user-provided content in calls to the following methods on a {@link scope scope}**:
+ * `$watch(userContent, ...)`
+ * `$watchGroup(userContent, ...)`
+ * `$watchCollection(userContent, ...)`
+ * `$eval(userContent)`
+ * `$evalAsync(userContent)`
+ * `$apply(userContent)`
+ * `$applyAsync(userContent)`
+* **Passing an expression generated from user-provided content in calls to services that parse expressions**:
+ * `$compile(userContent)`
+ * `$parse(userContent)`
+ * `$interpolate(userContent)`
+* **Passing an expression generated from user provided content as a predicate to `orderBy` pipe**:
+ `{{ value | orderBy : userContent }}`
+
+### Sandbox removal
+Each version of Angular 1 up to, but not including 1.6, contained an expression sandbox, which reduced the surface area of
+the vulnerability but never removed it. **In Angular 1.6 we removed this sandbox as developers kept relying upon it as a security
+feature even though it was always possible to access arbitrary JavaScript code if one could control the Angular templates
+or expressions of applications.**
+
+Control of the Angular templates makes applications vulnerable even if there was a completely secure sandbox:
+* https://ryhanson.com/stealing-session-tokens-on-plunker-with-an-angular-expression-injection/ in this blog post the author shows
+ a (now closed) vulnerability in the Plunker application due to server-side rendering inside an Angular template.
+* https://ryhanson.com/angular-expression-injection-walkthrough/ in this blog post the author describes an attack, which does not
+ rely upon an expression sandbox bypass, that can be made because the sample application is rendering a template on the server that
+ contains user entered content.
+
+**It's best to design your application in such a way that users cannot change client-side templates.**
* Do not mix client and server templates
* Do not use user input to generate templates dynamically
-* Do not run user input through `$scope.$eval`
+* Do not run user input through `$scope.$eval` (or any of the other expression parsing functions listed above)
* Consider using {@link ng.directive:ngCsp CSP} (but don't rely only on CSP)
+**You can use suitably sanitized server-side templating to dynamically generate CSS, URLs, etc, but not for generating templates that are
+bootstrapped/compiled by Angular.**
-### Mixing client-side and server-side templates
-
-In general, we recommend against this because it can create unintended XSS vectors.
-
-However, it's ok to mix server-side templating in the bootstrap template (`index.html`) as long
-as user input cannot be used on the server to output html that would then be processed by Angular
-in a way that would allow for arbitrary code execution.
-
-**For instance, you can use server-side templating to dynamically generate CSS, URLs, etc, but not
-for generating templates that are bootstrapped/compiled by Angular.**
+**If you must continue to allow user-provided content in an Angular template then the safest option is to ensure that it is only
+present in the part of the template that is made inert via the {@link ngNonBindable} directive.**
## HTTP Requests
diff --git a/src/ng/parse.js b/src/ng/parse.js
index a008dd1e4fea..ed0c4ca43afc 100644
--- a/src/ng/parse.js
+++ b/src/ng/parse.js
@@ -13,60 +13,23 @@
var $parseMinErr = minErr('$parse');
-var ARRAY_CTOR = [].constructor;
-var BOOLEAN_CTOR = (false).constructor;
-var FUNCTION_CTOR = Function.constructor;
-var NUMBER_CTOR = (0).constructor;
-var OBJECT_CTOR = {}.constructor;
-var STRING_CTOR = ''.constructor;
-var ARRAY_CTOR_PROTO = ARRAY_CTOR.prototype;
-var BOOLEAN_CTOR_PROTO = BOOLEAN_CTOR.prototype;
-var FUNCTION_CTOR_PROTO = FUNCTION_CTOR.prototype;
-var NUMBER_CTOR_PROTO = NUMBER_CTOR.prototype;
-var OBJECT_CTOR_PROTO = OBJECT_CTOR.prototype;
-var STRING_CTOR_PROTO = STRING_CTOR.prototype;
-
-var CALL = FUNCTION_CTOR_PROTO.call;
-var APPLY = FUNCTION_CTOR_PROTO.apply;
-var BIND = FUNCTION_CTOR_PROTO.bind;
-
-var objectValueOf = OBJECT_CTOR_PROTO.valueOf;
+var objectValueOf = {}.constructor.prototype.valueOf;
// Sandboxing Angular Expressions
// ------------------------------
-// Angular expressions are generally considered safe because these expressions only have direct
-// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by
-// obtaining a reference to native JS functions such as the Function constructor.
+// Angular expressions are no longer sandboxed. So it is now even easier to access arbitary JS code by
+// various means such as obtaining a reference to native JS functions like the Function constructor.
//
// As an example, consider the following Angular expression:
//
// {}.toString.constructor('alert("evil JS code")')
//
-// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
-// against the expression language, but not to prevent exploits that were enabled by exposing
-// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good
-// practice and therefore we are not even trying to protect against interaction with an object
-// explicitly exposed in this way.
-//
-// In general, it is not possible to access a Window object from an angular expression unless a
-// window or some DOM object that has a reference to window is published onto a Scope.
-// Similarly we prevent invocations of function known to be dangerous, as well as assignments to
-// native objects.
+// It is important to realise that if you create an expression from a string that contains user provided
+// content then it is possible that your application contains a security vulnerability to an XSS style attack.
//
// See https://docs.angularjs.org/guide/security
-function ensureSafeMemberName(name, fullExpression) {
- if (name === '__defineGetter__' || name === '__defineSetter__'
- || name === '__lookupGetter__' || name === '__lookupSetter__'
- || name === '__proto__') {
- throw $parseMinErr('isecfld',
- 'Attempting to access a disallowed field in Angular expressions! '
- + 'Expression: {0}', fullExpression);
- }
- return name;
-}
-
function getStringValue(name) {
// Property names must be strings. This means that non-string objects cannot be used
// as keys in an object. Any non-string object, including a number, is typecasted
@@ -85,67 +48,6 @@ function getStringValue(name) {
return name + '';
}
-function ensureSafeObject(obj, fullExpression) {
- // nifty check if obj is Function that is fast and works across iframes and other contexts
- if (obj) {
- if (obj.constructor === obj) {
- throw $parseMinErr('isecfn',
- 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
- fullExpression);
- } else if (// isWindow(obj)
- obj.window === obj) {
- throw $parseMinErr('isecwindow',
- 'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
- fullExpression);
- } else if (// isElement(obj)
- obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) {
- throw $parseMinErr('isecdom',
- 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
- fullExpression);
- } else if (// block Object so that we can't get hold of dangerous Object.* methods
- obj === Object) {
- throw $parseMinErr('isecobj',
- 'Referencing Object in Angular expressions is disallowed! Expression: {0}',
- fullExpression);
- }
- }
- return obj;
-}
-
-function ensureSafeFunction(obj, fullExpression) {
- if (obj) {
- if (obj.constructor === obj) {
- throw $parseMinErr('isecfn',
- 'Referencing Function in Angular expressions is disallowed! Expression: {0}',
- fullExpression);
- } else if (obj === CALL || obj === APPLY || obj === BIND) {
- throw $parseMinErr('isecff',
- 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}',
- fullExpression);
- }
- }
-}
-
-function ensureSafeAssignContext(obj, fullExpression) {
- if (obj) {
- if (obj === ARRAY_CTOR ||
- obj === BOOLEAN_CTOR ||
- obj === FUNCTION_CTOR ||
- obj === NUMBER_CTOR ||
- obj === OBJECT_CTOR ||
- obj === STRING_CTOR ||
- obj === ARRAY_CTOR_PROTO ||
- obj === BOOLEAN_CTOR_PROTO ||
- obj === FUNCTION_CTOR_PROTO ||
- obj === NUMBER_CTOR_PROTO ||
- obj === OBJECT_CTOR_PROTO ||
- obj === STRING_CTOR_PROTO) {
- throw $parseMinErr('isecaf',
- 'Assigning to a constructor or its prototype is disallowed! Expression: {0}',
- fullExpression);
- }
- }
-}
var OPERATORS = createMap();
forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
@@ -862,13 +764,12 @@ function ASTCompiler(astBuilder, $filter) {
}
ASTCompiler.prototype = {
- compile: function(expression, expensiveChecks) {
+ compile: function(expression) {
var self = this;
var ast = this.astBuilder.ast(expression);
this.state = {
nextId: 0,
filters: {},
- expensiveChecks: expensiveChecks,
fn: {vars: [], body: [], own: {}},
assign: {vars: [], body: [], own: {}},
inputs: []
@@ -911,21 +812,13 @@ ASTCompiler.prototype = {
// eslint-disable-next-line no-new-func
var fn = (new Function('$filter',
- 'ensureSafeMemberName',
- 'ensureSafeObject',
- 'ensureSafeFunction',
'getStringValue',
- 'ensureSafeAssignContext',
'ifDefined',
'plus',
'text',
fnString))(
this.$filter,
- ensureSafeMemberName,
- ensureSafeObject,
- ensureSafeFunction,
getStringValue,
- ensureSafeAssignContext,
ifDefined,
plusFn,
expression);
@@ -1042,7 +935,6 @@ ASTCompiler.prototype = {
nameId.computed = false;
nameId.name = ast.name;
}
- ensureSafeMemberName(ast.name);
self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)),
function() {
self.if_(self.stage === 'inputs' || 's', function() {
@@ -1055,9 +947,6 @@ ASTCompiler.prototype = {
});
}, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name))
);
- if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) {
- self.addEnsureSafeObject(intoId);
- }
recursionFn(intoId);
break;
case AST.MemberExpression:
@@ -1065,32 +954,24 @@ ASTCompiler.prototype = {
intoId = intoId || this.nextId();
self.recurse(ast.object, left, undefined, function() {
self.if_(self.notNull(left), function() {
- if (create && create !== 1) {
- self.addEnsureSafeAssignContext(left);
- }
if (ast.computed) {
right = self.nextId();
self.recurse(ast.property, right);
self.getStringValue(right);
- self.addEnsureSafeMemberName(right);
if (create && create !== 1) {
self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}'));
}
- expression = self.ensureSafeObject(self.computedMember(left, right));
+ expression = self.computedMember(left, right);
self.assign(intoId, expression);
if (nameId) {
nameId.computed = true;
nameId.name = right;
}
} else {
- ensureSafeMemberName(ast.property.name);
if (create && create !== 1) {
self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}'));
}
expression = self.nonComputedMember(left, ast.property.name);
- if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) {
- expression = self.ensureSafeObject(expression);
- }
self.assign(intoId, expression);
if (nameId) {
nameId.computed = false;
@@ -1122,21 +1003,16 @@ ASTCompiler.prototype = {
args = [];
self.recurse(ast.callee, right, left, function() {
self.if_(self.notNull(right), function() {
- self.addEnsureSafeFunction(right);
forEach(ast.arguments, function(expr) {
self.recurse(expr, ast.constant ? undefined : self.nextId(), undefined, function(argument) {
- args.push(self.ensureSafeObject(argument));
+ args.push(argument);
});
});
if (left.name) {
- if (!self.state.expensiveChecks) {
- self.addEnsureSafeObject(left.context);
- }
expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')';
} else {
expression = right + '(' + args.join(',') + ')';
}
- expression = self.ensureSafeObject(expression);
self.assign(intoId, expression);
}, function() {
self.assign(intoId, 'undefined');
@@ -1154,8 +1030,6 @@ ASTCompiler.prototype = {
this.recurse(ast.left, undefined, left, function() {
self.if_(self.notNull(left.context), function() {
self.recurse(ast.right, right);
- self.addEnsureSafeObject(self.member(left.context, left.name, left.computed));
- self.addEnsureSafeAssignContext(left.context);
expression = self.member(left.context, left.name, left.computed) + ast.operator + right;
self.assign(intoId, expression);
recursionFn(intoId || expression);
@@ -1303,42 +1177,10 @@ ASTCompiler.prototype = {
return this.nonComputedMember(left, right);
},
- addEnsureSafeObject: function(item) {
- this.current().body.push(this.ensureSafeObject(item), ';');
- },
-
- addEnsureSafeMemberName: function(item) {
- this.current().body.push(this.ensureSafeMemberName(item), ';');
- },
-
- addEnsureSafeFunction: function(item) {
- this.current().body.push(this.ensureSafeFunction(item), ';');
- },
-
- addEnsureSafeAssignContext: function(item) {
- this.current().body.push(this.ensureSafeAssignContext(item), ';');
- },
-
- ensureSafeObject: function(item) {
- return 'ensureSafeObject(' + item + ',text)';
- },
-
- ensureSafeMemberName: function(item) {
- return 'ensureSafeMemberName(' + item + ',text)';
- },
-
- ensureSafeFunction: function(item) {
- return 'ensureSafeFunction(' + item + ',text)';
- },
-
getStringValue: function(item) {
this.assign(item, 'getStringValue(' + item + ')');
},
- ensureSafeAssignContext: function(item) {
- return 'ensureSafeAssignContext(' + item + ',text)';
- },
-
lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
var self = this;
return function() {
@@ -1390,11 +1232,10 @@ function ASTInterpreter(astBuilder, $filter) {
}
ASTInterpreter.prototype = {
- compile: function(expression, expensiveChecks) {
+ compile: function(expression) {
var self = this;
var ast = this.astBuilder.ast(expression);
this.expression = expression;
- this.expensiveChecks = expensiveChecks;
findConstantAndWatchExpressions(ast, self.$filter);
var assignable;
var assign;
@@ -1465,20 +1306,17 @@ ASTInterpreter.prototype = {
context
);
case AST.Identifier:
- ensureSafeMemberName(ast.name, self.expression);
return self.identifier(ast.name,
- self.expensiveChecks || isPossiblyDangerousMemberName(ast.name),
context, create, self.expression);
case AST.MemberExpression:
left = this.recurse(ast.object, false, !!create);
if (!ast.computed) {
- ensureSafeMemberName(ast.property.name, self.expression);
right = ast.property.name;
}
if (ast.computed) right = this.recurse(ast.property);
return ast.computed ?
this.computedMember(left, right, context, create, self.expression) :
- this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression);
+ this.nonComputedMember(left, right, context, create, self.expression);
case AST.CallExpression:
args = [];
forEach(ast.arguments, function(expr) {
@@ -1499,13 +1337,11 @@ ASTInterpreter.prototype = {
var rhs = right(scope, locals, assign, inputs);
var value;
if (rhs.value != null) {
- ensureSafeObject(rhs.context, self.expression);
- ensureSafeFunction(rhs.value, self.expression);
var values = [];
for (var i = 0; i < args.length; ++i) {
- values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression));
+ values.push(args[i](scope, locals, assign, inputs));
}
- value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression);
+ value = rhs.value.apply(rhs.context, values);
}
return context ? {value: value} : value;
};
@@ -1515,8 +1351,6 @@ ASTInterpreter.prototype = {
return function(scope, locals, assign, inputs) {
var lhs = left(scope, locals, assign, inputs);
var rhs = right(scope, locals, assign, inputs);
- ensureSafeObject(lhs.value, self.expression);
- ensureSafeAssignContext(lhs.context);
lhs.context[lhs.name] = rhs;
return context ? {value: rhs} : rhs;
};
@@ -1708,16 +1542,13 @@ ASTInterpreter.prototype = {
value: function(value, context) {
return function() { return context ? {context: undefined, name: undefined, value: value} : value; };
},
- identifier: function(name, expensiveChecks, context, create, expression) {
+ identifier: function(name, context, create, expression) {
return function(scope, locals, assign, inputs) {
var base = locals && (name in locals) ? locals : scope;
if (create && create !== 1 && base && !(base[name])) {
base[name] = {};
}
var value = base ? base[name] : undefined;
- if (expensiveChecks) {
- ensureSafeObject(value, expression);
- }
if (context) {
return {context: base, name: name, value: value};
} else {
@@ -1733,15 +1564,12 @@ ASTInterpreter.prototype = {
if (lhs != null) {
rhs = right(scope, locals, assign, inputs);
rhs = getStringValue(rhs);
- ensureSafeMemberName(rhs, expression);
if (create && create !== 1) {
- ensureSafeAssignContext(lhs);
if (lhs && !(lhs[rhs])) {
lhs[rhs] = {};
}
}
value = lhs[rhs];
- ensureSafeObject(value, expression);
}
if (context) {
return {context: lhs, name: rhs, value: value};
@@ -1750,19 +1578,15 @@ ASTInterpreter.prototype = {
}
};
},
- nonComputedMember: function(left, right, expensiveChecks, context, create, expression) {
+ nonComputedMember: function(left, right, context, create, expression) {
return function(scope, locals, assign, inputs) {
var lhs = left(scope, locals, assign, inputs);
if (create && create !== 1) {
- ensureSafeAssignContext(lhs);
if (lhs && !(lhs[right])) {
lhs[right] = {};
}
}
var value = lhs != null ? lhs[right] : undefined;
- if (expensiveChecks || isPossiblyDangerousMemberName(right)) {
- ensureSafeObject(value, expression);
- }
if (context) {
return {context: lhs, name: right, value: value};
} else {
@@ -1794,14 +1618,10 @@ Parser.prototype = {
constructor: Parser,
parse: function(text) {
- return this.astCompiler.compile(text, this.options.expensiveChecks);
+ return this.astCompiler.compile(text);
}
};
-function isPossiblyDangerousMemberName(name) {
- return name === 'constructor';
-}
-
function getValueOf(value) {
return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value);
}
@@ -1859,8 +1679,7 @@ function getValueOf(value) {
* service.
*/
function $ParseProvider() {
- var cacheDefault = createMap();
- var cacheExpensive = createMap();
+ var cache = createMap();
var literals = {
'true': true,
'false': false,
@@ -1918,37 +1737,20 @@ function $ParseProvider() {
var noUnsafeEval = csp().noUnsafeEval;
var $parseOptions = {
csp: noUnsafeEval,
- expensiveChecks: false,
- literals: copy(literals),
- isIdentifierStart: isFunction(identStart) && identStart,
- isIdentifierContinue: isFunction(identContinue) && identContinue
- },
- $parseOptionsExpensive = {
- csp: noUnsafeEval,
- expensiveChecks: true,
literals: copy(literals),
isIdentifierStart: isFunction(identStart) && identStart,
isIdentifierContinue: isFunction(identContinue) && identContinue
};
- var runningChecksEnabled = false;
-
- $parse.$$runningExpensiveChecks = function() {
- return runningChecksEnabled;
- };
-
return $parse;
- function $parse(exp, interceptorFn, expensiveChecks) {
+ function $parse(exp, interceptorFn) {
var parsedExpression, oneTime, cacheKey;
- expensiveChecks = expensiveChecks || runningChecksEnabled;
-
switch (typeof exp) {
case 'string':
exp = exp.trim();
cacheKey = exp;
- var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
parsedExpression = cache[cacheKey];
if (!parsedExpression) {
@@ -1956,9 +1758,8 @@ function $ParseProvider() {
oneTime = true;
exp = exp.substring(2);
}
- var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions;
- var lexer = new Lexer(parseOptions);
- var parser = new Parser(lexer, $filter, parseOptions);
+ var lexer = new Lexer($parseOptions);
+ var parser = new Parser(lexer, $filter, $parseOptions);
parsedExpression = parser.parse(exp);
if (parsedExpression.constant) {
parsedExpression.$$watchDelegate = constantWatchDelegate;
@@ -1968,9 +1769,6 @@ function $ParseProvider() {
} else if (parsedExpression.inputs) {
parsedExpression.$$watchDelegate = inputsWatchDelegate;
}
- if (expensiveChecks) {
- parsedExpression = expensiveChecksInterceptor(parsedExpression);
- }
cache[cacheKey] = parsedExpression;
}
return addInterceptor(parsedExpression, interceptorFn);
@@ -1983,30 +1781,6 @@ function $ParseProvider() {
}
}
- function expensiveChecksInterceptor(fn) {
- if (!fn) return fn;
- expensiveCheckFn.$$watchDelegate = fn.$$watchDelegate;
- expensiveCheckFn.assign = expensiveChecksInterceptor(fn.assign);
- expensiveCheckFn.constant = fn.constant;
- expensiveCheckFn.literal = fn.literal;
- for (var i = 0; fn.inputs && i < fn.inputs.length; ++i) {
- fn.inputs[i] = expensiveChecksInterceptor(fn.inputs[i]);
- }
- expensiveCheckFn.inputs = fn.inputs;
-
- return expensiveCheckFn;
-
- function expensiveCheckFn(scope, locals, assign, inputs) {
- var expensiveCheckOldValue = runningChecksEnabled;
- runningChecksEnabled = true;
- try {
- return fn(scope, locals, assign, inputs);
- } finally {
- runningChecksEnabled = expensiveCheckOldValue;
- }
- }
- }
-
function expressionInputDirtyCheck(newValue, oldValueOfValue) {
if (newValue == null || oldValueOfValue == null) { // null/undefined
diff --git a/test/ng/directive/ngEventDirsSpec.js b/test/ng/directive/ngEventDirsSpec.js
index e2c2745f840e..d8288b828bbd 100644
--- a/test/ng/directive/ngEventDirsSpec.js
+++ b/test/ng/directive/ngEventDirsSpec.js
@@ -90,23 +90,13 @@ describe('event directives', function() {
});
- describe('security', function() {
+ describe('DOM event object', function() {
it('should allow access to the $event object', inject(function($rootScope, $compile) {
var scope = $rootScope.$new();
element = $compile('')(scope);
element.triggerHandler('click');
expect(scope.e.target).toBe(element[0]);
}));
-
- it('should block access to DOM nodes (e.g. exposed via $event)', inject(function($rootScope, $compile) {
- var scope = $rootScope.$new();
- element = $compile('')(scope);
- expect(function() {
- element.triggerHandler('click');
- }).toThrowMinErr(
- '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is disallowed! ' +
- 'Expression: e = $event.target');
- }));
});
describe('blur', function() {
diff --git a/test/ng/parseSpec.js b/test/ng/parseSpec.js
index ea216a644c75..79d49340da46 100644
--- a/test/ng/parseSpec.js
+++ b/test/ng/parseSpec.js
@@ -2422,770 +2422,6 @@ describe('parser', function() {
}));
- describe('sandboxing', function() {
- describe('Function constructor', function() {
- it('should not tranverse the Function constructor in the getter', function() {
- expect(function() {
- scope.$eval('{}.toString.constructor');
- }).toThrowMinErr(
- '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
- 'Expression: {}.toString.constructor');
- });
-
- it('should not allow access to the Function prototype in the getter', function() {
- expect(function() {
- scope.$eval('toString.constructor.prototype');
- }).toThrowMinErr(
- '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
- 'Expression: toString.constructor.prototype');
- });
-
- it('should NOT allow access to Function constructor in getter', function() {
- expect(function() {
- scope.$eval('{}.toString.constructor("alert(1)")');
- }).toThrowMinErr(
- '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
- 'Expression: {}.toString.constructor("alert(1)")');
- });
-
- it('should NOT allow access to Function constructor in setter', function() {
-
- expect(function() {
- scope.$eval('{}.toString.constructor.a = 1');
- }).toThrowMinErr(
- '$parse', 'isecfn','Referencing Function in Angular expressions is disallowed! ' +
- 'Expression: {}.toString.constructor.a = 1');
-
- expect(function() {
- scope.$eval('{}.toString["constructor"]["constructor"] = 1');
- }).toThrowMinErr(
- '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
- 'Expression: {}.toString["constructor"]["constructor"] = 1');
-
- scope.key1 = 'const';
- scope.key2 = 'ructor';
- expect(function() {
- scope.$eval('{}.toString[key1 + key2].foo = 1');
- }).toThrowMinErr(
- '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
- 'Expression: {}.toString[key1 + key2].foo = 1');
-
- expect(function() {
- scope.$eval('{}.toString["constructor"]["a"] = 1');
- }).toThrowMinErr(
- '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
- 'Expression: {}.toString["constructor"]["a"] = 1');
-
- scope.a = [];
- expect(function() {
- scope.$eval('a.toString.constructor = 1', scope);
- }).toThrowMinErr(
- '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
- 'Expression: a.toString.constructor');
- });
-
- it('should disallow traversing the Function object in a setter: E02', function() {
- expect(function() {
- // This expression by itself isn't dangerous. However, one can use this to
- // automatically call an object (e.g. a Function object) when it is automatically
- // toString'd/valueOf'd by setting the RHS to Function.prototype.call.
- scope.$eval('hasOwnProperty.constructor.prototype.valueOf = 1');
- }).toThrowMinErr(
- '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
- 'Expression: hasOwnProperty.constructor.prototype.valueOf');
- });
-
- it('should disallow passing the Function object as a parameter: E03', function() {
- expect(function() {
- // This expression constructs a function but does not execute it. It does lead the
- // way to execute it if one can get the toString/valueOf of it to call the function.
- scope.$eval('["a", "alert(1)"].sort(hasOwnProperty.constructor)');
- }).toThrow();
- });
-
- it('should prevent exploit E01', function() {
- // This is a tracking exploit. The two individual tests, it('should … : E02') and
- // it('should … : E03') test for two parts to block this exploit. This exploit works
- // as follows:
- //
- // • Array.sort takes a comparison function and passes it 2 parameters to compare. If
- // the result is non-primitive, sort then invokes valueOf() on the result.
- // • The Function object conveniently accepts two string arguments so we can use this
- // to construct a function. However, this doesn't do much unless we can execute it.
- // • We set the valueOf property on Function.prototype to Function.prototype.call.
- // This causes the function that we constructed to be executed when sort calls
- // .valueOf() on the result of the comparison.
- expect(function() {
- scope.$eval('' +
- 'hasOwnProperty.constructor.prototype.valueOf=valueOf.call;' +
- '["a","alert(1)"].sort(hasOwnProperty.constructor)');
- }).toThrow();
- });
-
- it('should NOT allow access to Function constructor that has been aliased in getters', function() {
- scope.foo = { 'bar': Function };
- expect(function() {
- scope.$eval('foo["bar"]');
- }).toThrowMinErr(
- '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
- 'Expression: foo["bar"]');
- });
-
- it('should NOT allow access to Function constructor that has been aliased in setters', function() {
- scope.foo = { 'bar': Function };
- expect(function() {
- scope.$eval('foo["bar"] = 1');
- }).toThrowMinErr(
- '$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
- 'Expression: foo["bar"] = 1');
- });
-
- describe('expensiveChecks', function() {
- it('should block access to window object even when aliased in getters', inject(function($parse, $window) {
- scope.foo = {w: $window};
- // This isn't blocked for performance.
- expect(scope.$eval($parse('foo.w'))).toBe($window);
- // Event handlers use the more expensive path for better protection since they expose
- // the $event object on the scope.
- expect(function() {
- scope.$eval($parse('foo.w', null, true));
- }).toThrowMinErr(
- '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
- 'Expression: foo.w');
- }));
-
- it('should block access to window object even when aliased in setters', inject(function($parse, $window) {
- scope.foo = {w: $window};
- // This is blocked as it points to `window`.
- expect(function() {
- expect(scope.$eval($parse('foo.w = 1'))).toBe($window);
- }).toThrowMinErr(
- '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
- 'Expression: foo.w = 1');
- // Event handlers use the more expensive path for better protection since they expose
- // the $event object on the scope.
- expect(function() {
- scope.$eval($parse('foo.w = 1', null, true));
- }).toThrowMinErr(
- '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
- 'Expression: foo.w = 1');
- }));
-
- they('should propagate expensive checks when calling $prop',
- ['foo.w && true',
- '$eval("foo.w && true")',
- 'this["$eval"]("foo.w && true")',
- 'bar;$eval("foo.w && true")',
- '$eval("foo.w && true");bar',
- '$eval("foo.w && true", null, false)',
- '$eval("foo");$eval("foo.w && true")',
- '$eval("$eval(\\"foo.w && true\\")")',
- '$eval("foo.e()")',
- '$evalAsync("foo.w && true")',
- 'this["$evalAsync"]("foo.w && true")',
- 'bar;$evalAsync("foo.w && true")',
- '$evalAsync("foo.w && true");bar',
- '$evalAsync("foo.w && true", null, false)',
- '$evalAsync("foo");$evalAsync("foo.w && true")',
- '$evalAsync("$evalAsync(\\"foo.w && true\\")")',
- '$evalAsync("foo.e()")',
- '$evalAsync("$eval(\\"foo.w && true\\")")',
- '$eval("$evalAsync(\\"foo.w && true\\")")',
- '$watch("foo.w && true")',
- '$watchCollection("foo.w && true", foo.f)',
- '$watchGroup(["foo.w && true"])',
- '$applyAsync("foo.w && true")'], function(expression) {
- inject(function($parse, $window) {
- scope.foo = {
- w: $window,
- bar: 'bar',
- e: function() { scope.$eval('foo.w && true'); },
- f: function() {}
- };
- expect($parse.$$runningExpensiveChecks()).toEqual(false);
- expect(function() {
- scope.$eval($parse(expression, null, true));
- scope.$digest();
- }).toThrowMinErr(
- '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is disallowed! ' +
- 'Expression: foo.w && true');
- expect($parse.$$runningExpensiveChecks()).toEqual(false);
- });
- });
-
- they('should restore the state of $$runningExpensiveChecks when the expression $prop throws',
- ['$eval("foo.t()")',
- '$evalAsync("foo.t()", {foo: foo})'], function(expression) {
- inject(function($parse, $window) {
- scope.foo = {
- t: function() { throw new Error(); }
- };
- expect($parse.$$runningExpensiveChecks()).toEqual(false);
- expect(function() {
- scope.$eval($parse(expression, null, true));
- scope.$digest();
- }).toThrow();
- expect($parse.$$runningExpensiveChecks()).toEqual(false);
- });
- });
-
- it('should handle `inputs` when running with expensive checks', inject(function($parse) {
- expect(function() {
- scope.$watch($parse('a + b', null, true), noop);
- scope.$digest();
- }).not.toThrow();
- }));
- });
- });
-
- describe('Function prototype functions', function() {
- it('should NOT allow invocation to Function.call', function() {
- scope.fn = Function.prototype.call;
-
- expect(function() {
- scope.$eval('$eval.call()');
- }).toThrowMinErr(
- '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
- 'Expression: $eval.call()');
-
- expect(function() {
- scope.$eval('fn()');
- }).toThrowMinErr(
- '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
- 'Expression: fn()');
- });
-
- it('should NOT allow invocation to Function.apply', function() {
- scope.apply = Function.prototype.apply;
-
- expect(function() {
- scope.$eval('$eval.apply()');
- }).toThrowMinErr(
- '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
- 'Expression: $eval.apply()');
-
- expect(function() {
- scope.$eval('apply()');
- }).toThrowMinErr(
- '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
- 'Expression: apply()');
- });
-
- it('should NOT allow invocation to Function.bind', function() {
- scope.bind = Function.prototype.bind;
-
- expect(function() {
- scope.$eval('$eval.bind()');
- }).toThrowMinErr(
- '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
- 'Expression: $eval.bind()');
-
- expect(function() {
- scope.$eval('bind()');
- }).toThrowMinErr(
- '$parse', 'isecff', 'Referencing call, apply or bind in Angular expressions is disallowed! ' +
- 'Expression: bind()');
- });
- });
-
- describe('Object constructor', function() {
-
- it('should NOT allow access to Object constructor that has been aliased in getters', function() {
- scope.foo = { 'bar': Object };
-
- expect(function() {
- scope.$eval('foo.bar.keys(foo)');
- }).toThrowMinErr(
- '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
- 'Expression: foo.bar.keys(foo)');
-
- expect(function() {
- scope.$eval('foo["bar"]["keys"](foo)');
- }).toThrowMinErr(
- '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
- 'Expression: foo["bar"]["keys"](foo)');
- });
-
- it('should NOT allow access to Object constructor that has been aliased in setters', function() {
- scope.foo = { 'bar': Object };
-
- expect(function() {
- scope.$eval('foo.bar.keys(foo).bar = 1');
- }).toThrowMinErr(
- '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
- 'Expression: foo.bar.keys(foo).bar = 1');
-
- expect(function() {
- scope.$eval('foo["bar"]["keys"](foo).bar = 1');
- }).toThrowMinErr(
- '$parse', 'isecobj', 'Referencing Object in Angular expressions is disallowed! ' +
- 'Expression: foo["bar"]["keys"](foo).bar = 1');
- });
- });
-
- describe('Window and $element/node', function() {
- it('should NOT allow access to the Window or DOM when indexing', inject(function($window, $document) {
- scope.wrap = {w: $window, d: $document};
-
- expect(function() {
- scope.$eval('wrap["w"]', scope);
- }).toThrowMinErr(
- '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
- 'disallowed! Expression: wrap["w"]');
- expect(function() {
- scope.$eval('wrap["d"]', scope);
- }).toThrowMinErr(
- '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
- 'disallowed! Expression: wrap["d"]');
- expect(function() {
- scope.$eval('wrap["w"] = 1', scope);
- }).toThrowMinErr(
- '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
- 'disallowed! Expression: wrap["w"] = 1');
- expect(function() {
- scope.$eval('wrap["d"] = 1', scope);
- }).toThrowMinErr(
- '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
- 'disallowed! Expression: wrap["d"] = 1');
- }));
-
- it('should NOT allow access to the Window or DOM returned from a function', inject(function($window, $document) {
- scope.getWin = valueFn($window);
- scope.getDoc = valueFn($document);
-
- expect(function() {
- scope.$eval('getWin()', scope);
- }).toThrowMinErr(
- '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
- 'disallowed! Expression: getWin()');
- expect(function() {
- scope.$eval('getDoc()', scope);
- }).toThrowMinErr(
- '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
- 'disallowed! Expression: getDoc()');
- }));
-
- it('should NOT allow calling functions on Window or DOM', inject(function($window, $document) {
- scope.a = {b: { win: $window, doc: $document }};
- expect(function() {
- scope.$eval('a.b.win.alert(1)', scope);
- }).toThrowMinErr(
- '$parse', 'isecwindow', 'Referencing the Window in Angular expressions is ' +
- 'disallowed! Expression: a.b.win.alert(1)');
- expect(function() {
- scope.$eval('a.b.doc.on("click")', scope);
- }).toThrowMinErr(
- '$parse', 'isecdom', 'Referencing DOM nodes in Angular expressions is ' +
- 'disallowed! Expression: a.b.doc.on("click")');
- }));
-
- // Issue #4805
- it('should NOT throw isecdom when referencing a Backbone Collection', function() {
- // Backbone stuff is sort of hard to mock, if you have a better way of doing this,
- // please fix this.
- var fakeBackboneCollection = {
- children: [{}, {}, {}],
- find: function() {},
- on: function() {},
- off: function() {},
- bind: function() {}
- };
- scope.backbone = fakeBackboneCollection;
- expect(function() { scope.$eval('backbone'); }).not.toThrow();
- });
-
- it('should NOT throw isecdom when referencing an array with node properties', function() {
- var array = [1,2,3];
- array.on = array.attr = array.prop = array.bind = true;
- scope.array = array;
- expect(function() { scope.$eval('array'); }).not.toThrow();
- });
- });
-
- describe('Disallowed fields', function() {
- it('should NOT allow access or invocation of __defineGetter__', function() {
- expect(function() {
- scope.$eval('{}.__defineGetter__');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}.__defineGetter__("a", "".charAt)');
- }).toThrowMinErr('$parse', 'isecfld');
-
- expect(function() {
- scope.$eval('{}["__defineGetter__"]');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}["__defineGetter__"]("a", "".charAt)');
- }).toThrowMinErr('$parse', 'isecfld');
-
- scope.a = '__define';
- scope.b = 'Getter__';
- expect(function() {
- scope.$eval('{}[a + b]');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}[a + b]("a", "".charAt)');
- }).toThrowMinErr('$parse', 'isecfld');
- });
-
- it('should NOT allow access or invocation of __defineSetter__', function() {
- expect(function() {
- scope.$eval('{}.__defineSetter__');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}.__defineSetter__("a", "".charAt)');
- }).toThrowMinErr('$parse', 'isecfld');
-
- expect(function() {
- scope.$eval('{}["__defineSetter__"]');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}["__defineSetter__"]("a", "".charAt)');
- }).toThrowMinErr('$parse', 'isecfld');
-
- scope.a = '__define';
- scope.b = 'Setter__';
- expect(function() {
- scope.$eval('{}[a + b]');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}[a + b]("a", "".charAt)');
- }).toThrowMinErr('$parse', 'isecfld');
- });
-
- it('should NOT allow access or invocation of __lookupGetter__', function() {
- expect(function() {
- scope.$eval('{}.__lookupGetter__');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}.__lookupGetter__("a")');
- }).toThrowMinErr('$parse', 'isecfld');
-
- expect(function() {
- scope.$eval('{}["__lookupGetter__"]');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}["__lookupGetter__"]("a")');
- }).toThrowMinErr('$parse', 'isecfld');
-
- scope.a = '__lookup';
- scope.b = 'Getter__';
- expect(function() {
- scope.$eval('{}[a + b]');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}[a + b]("a")');
- }).toThrowMinErr('$parse', 'isecfld');
- });
-
- it('should NOT allow access or invocation of __lookupSetter__', function() {
- expect(function() {
- scope.$eval('{}.__lookupSetter__');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}.__lookupSetter__("a")');
- }).toThrowMinErr('$parse', 'isecfld');
-
- expect(function() {
- scope.$eval('{}["__lookupSetter__"]');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}["__lookupSetter__"]("a")');
- }).toThrowMinErr('$parse', 'isecfld');
-
- scope.a = '__lookup';
- scope.b = 'Setter__';
- expect(function() {
- scope.$eval('{}[a + b]');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}[a + b]("a")');
- }).toThrowMinErr('$parse', 'isecfld');
- });
-
- it('should NOT allow access to __proto__', function() {
- expect(function() {
- scope.$eval('__proto__');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}.__proto__');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}.__proto__.foo = 1');
- }).toThrowMinErr('$parse', 'isecfld');
-
- expect(function() {
- scope.$eval('{}["__proto__"]');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}["__proto__"].foo = 1');
- }).toThrowMinErr('$parse', 'isecfld');
-
- expect(function() {
- scope.$eval('{}[["__proto__"]]');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}[["__proto__"]].foo = 1');
- }).toThrowMinErr('$parse', 'isecfld');
-
- expect(function() {
- scope.$eval('0[["__proto__"]]');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('0[["__proto__"]].foo = 1');
- }).toThrowMinErr('$parse', 'isecfld');
-
- scope.a = '__pro';
- scope.b = 'to__';
- expect(function() {
- scope.$eval('{}[a + b]');
- }).toThrowMinErr('$parse', 'isecfld');
- expect(function() {
- scope.$eval('{}[a + b].foo = 1');
- }).toThrowMinErr('$parse', 'isecfld');
- });
- });
-
- it('should prevent the exploit', function() {
- expect(function() {
- scope.$eval('(1)[{0: "__proto__", 1: "__proto__", 2: "__proto__", 3: "safe", length: 4, toString: [].pop}].foo = 1');
- }).toThrow();
- if (!msie || msie > 10) {
- // eslint-disable-next-line no-proto
- expect((1)['__proto__'].foo).toBeUndefined();
- }
- });
-
- it('should prevent the exploit', function() {
- expect(function() {
- scope.$eval('' +
- ' "".sub.call.call(' +
- '({})["constructor"].getOwnPropertyDescriptor("".sub.__proto__, "constructor").value,' +
- 'null,' +
- '"alert(1)"' +
- ')()' +
- '');
- }).toThrow();
- });
-
- they('should prevent assigning in the context of the $prop constructor', {
- Array: [[], '[]'],
- Boolean: [true, '(true)'],
- Number: [1, '(1)'],
- String: ['string', '"string"']
- }, function(values) {
- var thing = values[0];
- var expr = values[1];
- var constructorExpr = expr + '.constructor';
-
- expect(function() {
- scope.$eval(constructorExpr + '.join');
- }).not.toThrow();
- expect(function() {
- delete scope.foo;
- scope.$eval('foo = ' + constructorExpr + '.join');
- }).not.toThrow();
- expect(function() {
- scope.$eval(constructorExpr + '.join = ""');
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- scope.$eval(constructorExpr + '[0] = ""');
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- delete scope.foo;
- scope.$eval('foo = ' + constructorExpr + '; foo.join = ""');
- }).toThrowMinErr('$parse', 'isecaf');
-
- expect(function() {
- scope.foo = thing;
- scope.$eval('foo.constructor[0] = ""');
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- delete scope.foo;
- scope.$eval('foo.constructor[0] = ""', {foo: thing});
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- scope.foo = thing.constructor;
- scope.$eval('foo[0] = ""');
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- delete scope.foo;
- scope.$eval('foo[0] = ""', {foo: thing.constructor});
- }).toThrowMinErr('$parse', 'isecaf');
- });
-
- they('should prevent assigning in the context of the $prop constructor', {
- // These might throw different error (e.g. isecobj, isecfn),
- // but still having them here for good measure
- Function: [noop, '$eval'],
- Object: [{}, '{}']
- }, function(values) {
- var thing = values[0];
- var expr = values[1];
- var constructorExpr = expr + '.constructor';
-
- expect(function() {
- scope.$eval(constructorExpr + '.join');
- }).not.toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- delete scope.foo;
- scope.$eval('foo = ' + constructorExpr + '.join');
- }).not.toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- scope.$eval(constructorExpr + '.join = ""');
- }).toThrow();
- expect(function() {
- scope.$eval(constructorExpr + '[0] = ""');
- }).toThrow();
- expect(function() {
- delete scope.foo;
- scope.$eval('foo = ' + constructorExpr + '; foo.join = ""');
- }).toThrow();
-
- expect(function() {
- scope.foo = thing;
- scope.$eval('foo.constructor[0] = ""');
- }).toThrow();
- expect(function() {
- delete scope.foo;
- scope.$eval('foo.constructor[0] = ""', {foo: thing});
- }).toThrow();
- expect(function() {
- scope.foo = thing.constructor;
- scope.$eval('foo[0] = ""');
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- delete scope.foo;
- scope.$eval('foo[0] = ""', {foo: thing.constructor});
- }).toThrowMinErr('$parse', 'isecaf');
- });
-
- it('should prevent assigning only in the context of an actual constructor', function() {
- // foo.constructor is not a constructor.
- expect(function() {
- delete scope.foo;
- scope.$eval('foo.constructor[0] = ""', {foo: {constructor: ''}});
- }).not.toThrow();
-
- expect(function() {
- scope.$eval('"a".constructor.prototype.charAt = [].join');
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- scope.$eval('"a".constructor.prototype.charCodeAt = [].concat');
- }).toThrowMinErr('$parse', 'isecaf');
- });
-
- they('should prevent assigning in the context of the $prop constructor prototype', {
- Array: [[], '[]'],
- Boolean: [true, '(true)'],
- Number: [1, '(1)'],
- String: ['string', '"string"']
- }, function(values) {
- var thing = values[0];
- var expr = values[1];
- var constructorExpr = expr + '.constructor';
- var prototypeExpr = constructorExpr + '.prototype';
-
- expect(function() {
- scope.$eval(prototypeExpr + '.boin');
- }).not.toThrow();
- expect(function() {
- delete scope.foo;
- scope.$eval('foo = ' + prototypeExpr + '.boin');
- }).not.toThrow();
- expect(function() {
- scope.$eval(prototypeExpr + '.boin = ""');
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- scope.$eval(prototypeExpr + '[0] = ""');
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- delete scope.foo;
- scope.$eval('foo = ' + constructorExpr + '; foo.prototype.boin = ""');
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- delete scope.foo;
- scope.$eval('foo = ' + prototypeExpr + '; foo.boin = ""');
- }).toThrowMinErr('$parse', 'isecaf');
-
- expect(function() {
- scope.foo = thing.constructor;
- scope.$eval('foo.prototype[0] = ""');
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- delete scope.foo;
- scope.$eval('foo.prototype[0] = ""', {foo: thing.constructor});
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- scope.foo = thing.constructor.prototype;
- scope.$eval('foo[0] = ""');
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- delete scope.foo;
- scope.$eval('foo[0] = ""', {foo: thing.constructor.prototype});
- }).toThrowMinErr('$parse', 'isecaf');
- });
-
- they('should prevent assigning in the context of a constructor prototype', {
- // These might throw different error (e.g. isecobj, isecfn),
- // but still having them here for good measure
- Function: [noop, '$eval'],
- Object: [{}, '{}']
- }, function(values) {
- var thing = values[0];
- var expr = values[1];
- var constructorExpr = expr + '.constructor';
- var prototypeExpr = constructorExpr + '.prototype';
-
- expect(function() {
- scope.$eval(prototypeExpr + '.boin');
- }).not.toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- delete scope.foo;
- scope.$eval('foo = ' + prototypeExpr + '.boin');
- }).not.toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- scope.$eval(prototypeExpr + '.boin = ""');
- }).toThrow();
- expect(function() {
- scope.$eval(prototypeExpr + '[0] = ""');
- }).toThrow();
- expect(function() {
- delete scope.foo;
- scope.$eval('foo = ' + constructorExpr + '; foo.prototype.boin = ""');
- }).toThrow();
- expect(function() {
- delete scope.foo;
- scope.$eval('foo = ' + prototypeExpr + '; foo.boin = ""');
- }).toThrow();
-
- expect(function() {
- scope.foo = thing.constructor;
- scope.$eval('foo.prototype[0] = ""');
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- delete scope.foo;
- scope.$eval('foo.prototype[0] = ""', {foo: thing.constructor});
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- scope.foo = thing.constructor.prototype;
- scope.$eval('foo[0] = ""');
- }).toThrowMinErr('$parse', 'isecaf');
- expect(function() {
- delete scope.foo;
- scope.$eval('foo[0] = ""', {foo: thing.constructor.prototype});
- }).toThrowMinErr('$parse', 'isecaf');
- });
-
- it('should prevent assigning only in the context of an actual prototype', function() {
- // foo.constructor.prototype is not a constructor prototype.
- expect(function() {
- delete scope.foo;
- scope.$eval('foo.constructor.prototype[0] = ""', {foo: {constructor: {prototype: ''}}});
- }).not.toThrow();
- });
- });
-
it('should call the function from the received instance and not from a new one', function() {
var n = 0;
scope.fn = function() {