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

fix($parse): block assigning to fields of a constructor #12860

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/ng/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ function ensureSafeFunction(obj, fullExpression) {
}
}

function ensureSafeAssignContext(obj, fullExpression) {
if (obj) {
if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
throw $parseMinErr('isecaf',
'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we only care about built-in constructors?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we care the ones an expression can invoke and can be used to break the sandbox... the only other I can think about is Scope, but I really have no idea if it can be maliciously used. The alternative would be blocking to any property named constructor, but it might be just too much and there might be valid uses.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I guess we can't protect against developers putting stuff on the scope via a controller, such as the window object, for instance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are other protections in place that prevent any form of access to several objects. Eg. DOM nodes and window

}

var OPERATORS = createMap();
forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; });
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
Expand Down Expand Up @@ -816,6 +826,7 @@ ASTCompiler.prototype = {
'ensureSafeMemberName',
'ensureSafeObject',
'ensureSafeFunction',
'ensureSafeAssignContext',
'ifDefined',
'plus',
'text',
Expand All @@ -824,6 +835,7 @@ ASTCompiler.prototype = {
ensureSafeMemberName,
ensureSafeObject,
ensureSafeFunction,
ensureSafeAssignContext,
ifDefined,
plusFn,
expression);
Expand Down Expand Up @@ -1050,6 +1062,7 @@ ASTCompiler.prototype = {
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);
Expand Down Expand Up @@ -1175,6 +1188,10 @@ ASTCompiler.prototype = {
this.current().body.push(this.ensureSafeFunction(item), ';');
},

addEnsureSafeAssignContext: function(item) {
this.current().body.push(this.ensureSafeAssignContext(item), ';');
},

ensureSafeObject: function(item) {
return 'ensureSafeObject(' + item + ',text)';
},
Expand All @@ -1187,6 +1204,10 @@ ASTCompiler.prototype = {
return 'ensureSafeFunction(' + item + ',text)';
},

ensureSafeAssignContext: function(item) {
return 'ensureSafeAssignContext(' + item + ',text)';
},

lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) {
var self = this;
return function() {
Expand Down Expand Up @@ -1364,6 +1385,7 @@ ASTInterpreter.prototype = {
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;
};
Expand Down
26 changes: 26 additions & 0 deletions test/ng/parseSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2703,6 +2703,32 @@ describe('parser', function() {
'');
}).toThrow();
});

it('should prevent assigning in the context of a constructor', function() {
expect(function() {
scope.$eval("''.constructor.join");
}).not.toThrow();
expect(function() {
scope.$eval("''.constructor.join = ''.constructor.join");
}).toThrow();
expect(function() {
scope.$eval("''.constructor[0] = ''");
}).toThrow();
expect(function() {
scope.$eval("(0).constructor[0] = ''");
}).toThrow();
expect(function() {
scope.$eval("{}.constructor[0] = ''");
}).toThrow();
// foo.constructor is the object constructor.
expect(function() {
scope.$eval("foo.constructor[0] = ''", {foo: {}});
}).toThrow();
// foo.constructor is not a constructor.
expect(function() {
scope.$eval("foo.constructor[0] = ''", {foo: {constructor: ''}});
}).not.toThrow();
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For good measure we could also test assigning to a variable that has been set to a constructor:

expect(function() {
  scope.$eval("foo = {}.constructor; foo.join = ''");
}).toThrow();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

});

it('should call the function from the received instance and not from a new one', function() {
Expand Down