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

Commit

Permalink
refactor($parse): move around previous security changes made to $parse
Browse files Browse the repository at this point in the history
  • Loading branch information
rodyhaddad authored and IgorMinar committed Jun 30, 2014
1 parent 6081f20 commit db713a1
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 204 deletions.
27 changes: 18 additions & 9 deletions docs/content/error/$parse/isecfld.ngdoc
@@ -1,18 +1,27 @@
@ngdoc error
@name $parse:isecfld
@fullName Referencing 'constructor' Field in Expression
@fullName Referencing Disallowed Field in Expression
@description

Occurs when an expression attempts to access an objects constructor field.
Occurs when an expression attempts to access one of the following fields:

AngularJS bans constructor access from within expressions since constructor
access is a known way to execute arbitrary Javascript code.
* __proto__
* __defineGetter__
* __defineSetter__
* __lookupGetter__
* __lookupSetter__

To resolve this error, avoid constructor access. As a last resort, alias
the constructor and access it through the alias instead.
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.

Example expression that would result in this error:
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:

```
<div>{{user.constructor.name}}</div>
```
<div>{{user.__proto__.hasOwnProperty = $emit}}</div>

<div>{{user.__defineGetter__('name', noop)}}</div>
```
11 changes: 11 additions & 0 deletions docs/content/error/$parse/isecobj.ngdoc
@@ -0,0 +1,11 @@
@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.
59 changes: 25 additions & 34 deletions src/ng/parse.js
Expand Up @@ -10,39 +10,26 @@ var $parseMinErr = minErr('$parse');
//
// As an example, consider the following Angular expression:
//
// {}.toString.constructor(alert("evil JS code"))
//
// We want to prevent this type of access. For the sake of performance, during the lexing phase we
// disallow any "dotted" access to any member named "constructor".
//
// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor
// while evaluating the expression, which is a stronger but more expensive test. Since reflective
// calls are expensive anyway, this is not such a big deal compared to static dereferencing.
// {}.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.
//
// A developer could foil the name check by aliasing the Function constructor under a different
// name on the scope.
//
// 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.


function ensureSafeMemberName(name, fullExpression) {
if (name === "constructor") {
if (name === "__defineGetter__" || name === "__defineSetter__"
|| name === "__lookupGetter__" || name === "__lookupSetter__"
|| name === "__proto__") {
throw $parseMinErr('isecfld',
'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else if (name === "__defineGetter__" || name === "__defineSetter__"
|| name === "__lookupGetter__" || name === "__lookupSetter__") {
throw $parseMinErr('isecgetset',
'Defining and looking up getters and setters in Angular expressions is disallowed! '
+'Expression: {0}', fullExpression);
} else if (name === "__proto__") {
throw $parseMinErr('isecproto', 'Using __proto__ in Angular expressions is disallowed! '
'Attempting to access a disallowed field in Angular expressions! '
+'Expression: {0}', fullExpression);
}
return name;
Expand All @@ -56,7 +43,7 @@ function ensureSafeObject(obj, fullExpression) {
'Referencing Function in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else if (// isWindow(obj)
obj.document && obj.location && obj.alert && obj.setInterval) {
obj.window === obj) {
throw $parseMinErr('isecwindow',
'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
fullExpression);
Expand All @@ -65,21 +52,26 @@ function ensureSafeObject(obj, fullExpression) {
throw $parseMinErr('isecdom',
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else if (// isObject(obj)
obj.getOwnPropertyNames || obj.getOwnPropertyDescriptor) {
} 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);
} else if (obj === ({}).__defineGetter__ || obj === ({}).__defineSetter__
|| obj === ({}).__lookupGetter__ || obj === ({}).__lookupSetter__) {
throw $parseMinErr('isecgetset',
'Defining and looking up getters and setters 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);
}
}
}

var OPERATORS = {
/* jshint bitwise : false */
'null':function(){return null;},
Expand Down Expand Up @@ -699,10 +691,7 @@ Parser.prototype = {
i = indexFn(self, locals),
v;

if (i === "__proto__") {
throw $parseMinErr('isecproto', 'Using __proto__ in Angular expressions is disallowed! '
+'Expression: {0}', parser.text);
}
ensureSafeMemberName(i, parser.text);
if (!o) return undefined;
v = ensureSafeObject(o[i], parser.text);
return v;
Expand Down Expand Up @@ -737,7 +726,7 @@ Parser.prototype = {
var fnPtr = fn(scope, locals, context) || noop;

ensureSafeObject(context, parser.text);
ensureSafeObject(fnPtr, parser.text);
ensureSafeFunction(fnPtr, parser.text);

// IE stupidity! (IE doesn't have apply for some native functions)
var v = fnPtr.apply
Expand Down Expand Up @@ -832,6 +821,8 @@ function setter(obj, path, setValue, fullExp) {
obj = propertyObj;
}
key = ensureSafeMemberName(element.shift(), fullExp);
ensureSafeObject(obj, fullExp);
ensureSafeObject(obj[key], fullExp);
obj[key] = setValue;
return setValue;
}
Expand Down

0 comments on commit db713a1

Please sign in to comment.