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 prototype #14951

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
12 changes: 12 additions & 0 deletions docs/content/error/$parse/isecaf.ngdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@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.
24 changes: 21 additions & 3 deletions src/ng/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,28 @@ 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) {
var booleanConstructor = (false).constructor;
var numberConstructor = (0).constructor;
var stringConstructor = ''.constructor;
var objectConstructor = {}.constructor;
var arrayConstructor = [].constructor;
var functionConstructor = Function.constructor;

if (obj === booleanConstructor ||
obj === numberConstructor ||
obj === stringConstructor ||
obj === objectConstructor ||
obj === arrayConstructor ||
obj === functionConstructor ||
obj === booleanConstructor.prototype ||
obj === numberConstructor.prototype ||
obj === stringConstructor.prototype ||
obj === objectConstructor.prototype ||
obj === arrayConstructor.prototype ||
obj === functionConstructor.prototype) {
throw $parseMinErr('isecaf',
'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
'Assigning to a constructor or its prototype is disallowed! Expression: {0}',
fullExpression);
}
}
}
Expand Down
206 changes: 194 additions & 12 deletions test/ng/parseSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2957,39 +2957,221 @@ describe('parser', function() {
}).toThrow();
});

it('should prevent assigning in the context of a constructor', function() {
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() {
scope.$eval("''.constructor.join");
delete scope.foo;
scope.$eval('foo = ' + constructorExpr + '.join');
}).not.toThrow();
expect(function() {
scope.$eval("''.constructor.join = ''.constructor.join");
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("''.constructor[0] = ''");
scope.$eval(constructorExpr + '[0] = ""');
}).toThrow();
expect(function() {
scope.$eval("(0).constructor[0] = ''");
delete scope.foo;
scope.$eval('foo = ' + constructorExpr + '; foo.join = ""');
}).toThrow();

expect(function() {
scope.$eval("{}.constructor[0] = ''");
scope.foo = thing;
scope.$eval('foo.constructor[0] = ""');
}).toThrow();
// foo.constructor is the object constructor.
expect(function() {
scope.$eval("foo.constructor[0] = ''", {foo: {}});
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() {
scope.$eval("foo.constructor[0] = ''", {foo: {constructor: ''}});
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("objConstructor = {}.constructor; objConstructor.join = ''");
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("'a'.constructor.prototype.charAt=[].join");
scope.$eval(prototypeExpr + '[0] = ""');
}).toThrow();
expect(function() {
scope.$eval("'a'.constructor.prototype.charCodeAt=[].concat");
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();
});
});

Expand Down