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

Commit afb65c1

Browse files
committed
fix($parse): do not convert to string computed properties multiple times
Do not convert to string properties multiple times.
1 parent 9d5ac2e commit afb65c1

File tree

2 files changed

+42
-11
lines changed

2 files changed

+42
-11
lines changed

src/ng/parse.js

+21-11
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,31 @@ var promiseWarning;
2929

3030

3131
function ensureSafeMemberName(name, fullExpression) {
32+
if (name === "__defineGetter__" || name === "__defineSetter__"
33+
|| name === "__lookupGetter__" || name === "__lookupSetter__"
34+
|| name === "__proto__") {
35+
throw $parseMinErr('isecfld',
36+
'Attempting to access a disallowed field in Angular expressions! '
37+
+ 'Expression: {0}', fullExpression);
38+
}
39+
return name;
40+
}
41+
42+
function getStringValue(name, fullExpression) {
3243
// From the JavaScript docs:
3344
// Property names must be strings. This means that non-string objects cannot be used
3445
// as keys in an object. Any non-string object, including a number, is typecasted
3546
// into a string via the toString method.
3647
//
3748
// So, to ensure that we are checking the same `name` that JavaScript would use,
38-
// we cast it to a string, if possible
39-
name = (isObject(name) && name.toString) ? name.toString() : name;
40-
41-
if (name === "__defineGetter__" || name === "__defineSetter__"
42-
|| name === "__lookupGetter__" || name === "__lookupSetter__"
43-
|| name === "__proto__") {
44-
throw $parseMinErr('isecfld',
45-
'Attempting to access a disallowed field in Angular expressions! '
46-
+'Expression: {0}', fullExpression);
49+
// we cast it to a string, if possible.
50+
// Doing `name + ''` can cause a repl error if the result to `toString` is not a string,
51+
// this is, this will handle objects that misbehave.
52+
name = name + '';
53+
if (!isString(name)) {
54+
throw $parseMinErr('iseccst',
55+
'Cannot convert object to primitive value! '
56+
+ 'Expression: {0}', fullExpression);
4757
}
4858
return name;
4959
}
@@ -722,7 +732,7 @@ Parser.prototype = {
722732

723733
return extend(function(self, locals) {
724734
var o = obj(self, locals),
725-
i = indexFn(self, locals),
735+
i = getStringValue(indexFn(self, locals), parser.text),
726736
v, p;
727737

728738
ensureSafeMemberName(i, parser.text);
@@ -739,7 +749,7 @@ Parser.prototype = {
739749
return v;
740750
}, {
741751
assign: function(self, value, locals) {
742-
var key = ensureSafeMemberName(indexFn(self, locals), parser.text);
752+
var key = ensureSafeMemberName(getStringValue(indexFn(self, locals), parser.text), parser.text);
743753
// prevent overwriting of Function.constructor which would break ensureSafeObject check
744754
var o = ensureSafeObject(obj(self, locals), parser.text);
745755
if (!o) obj.assign(self, o = {});

test/ng/parseSpec.js

+21
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,27 @@ describe('parser', function() {
11191119
expect(count).toBe(1);
11201120
});
11211121

1122+
it('should prevent the exploit', function() {
1123+
expect(function() {
1124+
scope.$eval('(1)[{0: "__proto__", 1: "__proto__", 2: "__proto__", 3: "safe", length: 4, toString: [].pop}].foo = 1');
1125+
}).toThrow();
1126+
if (!msie || msie > 10) {
1127+
expect((1)['__proto__'].foo).toBeUndefined();
1128+
}
1129+
});
1130+
1131+
it('should prevent the exploit', function() {
1132+
expect(function() {
1133+
scope.$eval('' +
1134+
' "".sub.call.call(' +
1135+
'({})["constructor"].getOwnPropertyDescriptor("".sub.__proto__, "constructor").value,' +
1136+
'null,' +
1137+
'"alert(1)"' +
1138+
')()' +
1139+
'');
1140+
}).toThrow();
1141+
});
1142+
11221143

11231144
it('should call the function once when it is part of the context on array lookups', function() {
11241145
var count = 0;

0 commit comments

Comments
 (0)