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

Commit 4ab16aa

Browse files
committed
feat($parse): revert hiding "private" properties
Hiding `_*` properties was a feature primarily for developers using Closure compiler and Google JS style. We didn't realize how many people will be affected by this change. We might introduce this feature in the future, probably under a config option, but it needs more research and so I'm reverting the change for now. This reverts commit 3d6a89e. Closes #4926 Closes #4842 Closes #4865 Closes #4859 Closes #4849 Conflicts: src/ng/parse.js
1 parent 89f435d commit 4ab16aa

File tree

3 files changed

+11
-140
lines changed

3 files changed

+11
-140
lines changed

docs/content/error/parse/isecprv.ngdoc

-50
This file was deleted.

src/ng/parse.js

+9-25
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,18 @@ var promiseWarning;
88
// ------------------------------
99
// Angular expressions are generally considered safe because these expressions only have direct
1010
// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by
11-
// obtaining a reference to native JS functions such as the Function constructor, the global Window
12-
// or Document object. In addition, many powerful functions for use by JavaScript code are
13-
// published on scope that shouldn't be available from within an Angular expression.
11+
// obtaining a reference to native JS functions such as the Function constructor.
1412
//
1513
// As an example, consider the following Angular expression:
1614
//
1715
// {}.toString.constructor(alert("evil JS code"))
1816
//
1917
// We want to prevent this type of access. For the sake of performance, during the lexing phase we
20-
// disallow any "dotted" access to any member named "constructor" or to any member whose name begins
21-
// or ends with an underscore. The latter allows one to exclude the private / JavaScript only API
22-
// available on the scope and controllers from the context of an Angular expression.
18+
// disallow any "dotted" access to any member named "constructor".
2319
//
24-
// For reflective calls (a[b]), we check that the value of the lookup is not the Function
25-
// constructor, Window or DOM node while evaluating the expression, which is a stronger but more
26-
// expensive test. Since reflective calls are expensive anyway, this is not such a big deal compared
27-
// to static dereferencing.
20+
// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor
21+
// while evaluating the expression, which is a stronger but more expensive test. Since reflective
22+
// calls are expensive anyway, this is not such a big deal compared to static dereferencing.
2823
//
2924
// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
3025
// against the expression language, but not to prevent exploits that were enabled by exposing
@@ -38,20 +33,12 @@ var promiseWarning;
3833
// In general, it is not possible to access a Window object from an angular expression unless a
3934
// window or some DOM object that has a reference to window is published onto a Scope.
4035

41-
function ensureSafeMemberName(name, fullExpression, allowConstructor) {
42-
if (typeof name !== 'string' && toString.apply(name) !== "[object String]") {
43-
return name;
44-
}
45-
if (name === "constructor" && !allowConstructor) {
36+
function ensureSafeMemberName(name, fullExpression) {
37+
if (name === "constructor") {
4638
throw $parseMinErr('isecfld',
4739
'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}',
4840
fullExpression);
4941
}
50-
if (name.charAt(0) === '_' || name.charAt(name.length-1) === '_') {
51-
throw $parseMinErr('isecprv',
52-
'Referencing private fields in Angular expressions is disallowed! Expression: {0}',
53-
fullExpression);
54-
}
5542
return name;
5643
}
5744

@@ -735,10 +722,7 @@ Parser.prototype = {
735722

736723
return extend(function(self, locals) {
737724
var o = obj(self, locals),
738-
// In the getter, we will not block looking up "constructor" by name in order to support user defined
739-
// constructors. However, if value looked up is the Function constructor, we will still block it in the
740-
// ensureSafeObject call right after we look up o[i] (a few lines below.)
741-
i = ensureSafeMemberName(indexFn(self, locals), parser.text, true /* allowConstructor */),
725+
i = indexFn(self, locals),
742726
v, p;
743727

744728
if (!o) return undefined;
@@ -754,7 +738,7 @@ Parser.prototype = {
754738
return v;
755739
}, {
756740
assign: function(self, value, locals) {
757-
var key = ensureSafeMemberName(indexFn(self, locals), parser.text);
741+
var key = indexFn(self, locals);
758742
// prevent overwriting of Function.constructor which would break ensureSafeObject check
759743
var safe = ensureSafeObject(obj(self, locals), parser.text);
760744
return safe[key] = value;

test/ng/parseSpec.js

+2-65
Original file line numberDiff line numberDiff line change
@@ -591,57 +591,6 @@ describe('parser', function() {
591591
});
592592

593593
describe('sandboxing', function() {
594-
describe('private members', function() {
595-
it('should NOT allow access to private members', function() {
596-
forEach(['_name', 'name_', '_', '_name_'], function(name) {
597-
function _testExpression(expression) {
598-
scope.a = {b: name};
599-
scope[name] = {a: scope.a};
600-
scope.piece_1 = "XX" + name.charAt(0) + "XX";
601-
scope.piece_2 = "XX" + name.substr(1) + "XX";
602-
expect(function() {
603-
scope.$eval(expression);
604-
}).toThrowMinErr(
605-
'$parse', 'isecprv', 'Referencing private fields in Angular expressions is disallowed! ' +
606-
'Expression: ' + expression);
607-
}
608-
609-
function testExpression(expression) {
610-
if (expression.indexOf('"NAME"') != -1) {
611-
var concatExpr = 'piece_1.substr(2, 1) + piece_2.substr(2, LEN)'.replace('LEN', name.length-1);
612-
_testExpression(expression.replace(/"NAME"/g, concatExpr));
613-
_testExpression(expression.replace(/"NAME"/g, '(' + concatExpr + ')'));
614-
}
615-
_testExpression(expression.replace(/NAME/g, name));
616-
}
617-
618-
// Not all of these are exploitable. The tests ensure that the contract is honored
619-
// without caring about the implementation or exploitability.
620-
testExpression('NAME'); testExpression('NAME = 1');
621-
testExpression('(NAME)'); testExpression('(NAME) = 1');
622-
testExpression('a.NAME'); testExpression('a.NAME = 1');
623-
testExpression('NAME.b'); testExpression('NAME.b = 1');
624-
testExpression('a.NAME.b'); testExpression('a.NAME.b = 1');
625-
testExpression('NAME()'); testExpression('NAME() = 1');
626-
testExpression('(NAME)()'); testExpression('(NAME = 1)()');
627-
testExpression('(NAME).foo()'); testExpression('(NAME = 1).foo()');
628-
testExpression('a.NAME()'); testExpression('a.NAME() = 1');
629-
testExpression('a.NAME.foo()'); testExpression('a.NAME.foo()');
630-
testExpression('foo(NAME)'); testExpression('foo(NAME = 1)');
631-
testExpression('foo(a.NAME)'); testExpression('foo(a.NAME = 1)');
632-
testExpression('foo(1, a.NAME)'); testExpression('foo(1, a.NAME = 1)');
633-
testExpression('foo(a["NAME"])'); testExpression('foo(a["NAME"] = 1)');
634-
testExpression('foo(1, a["NAME"])'); testExpression('foo(1, a["NAME"] = 1)');
635-
testExpression('foo(b = a["NAME"])'); testExpression('foo(b = (a["NAME"] = 1))');
636-
testExpression('a["NAME"]'); testExpression('a["NAME"] = 1');
637-
testExpression('a["NAME"]()');
638-
testExpression('a["NAME"].foo()');
639-
testExpression('a.b["NAME"]'); testExpression('a.b["NAME"] = 1');
640-
testExpression('a["b"]["NAME"]'); testExpression('a["b"]["NAME"] = 1');
641-
});
642-
});
643-
});
644-
645594
describe('Function constructor', function() {
646595
it('should NOT allow access to Function constructor in getter', function() {
647596
expect(function() {
@@ -702,29 +651,17 @@ describe('parser', function() {
702651
expect(function() {
703652
scope.$eval('{}.toString["constructor"]["constructor"] = 1');
704653
}).toThrowMinErr(
705-
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
654+
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
706655
'Expression: {}.toString["constructor"]["constructor"] = 1');
707656

708657

709658
scope.key1 = "const";
710659
scope.key2 = "ructor";
711-
expect(function() {
712-
scope.$eval('{}.toString[key1 + key2].foo');
713-
}).toThrowMinErr(
714-
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
715-
'Expression: {}.toString[key1 + key2].foo');
716-
717-
expect(function() {
718-
scope.$eval('{}.toString[key1 + key2] = 1');
719-
}).toThrowMinErr(
720-
'$parse', 'isecfld', 'Referencing "constructor" field in Angular expressions is disallowed! ' +
721-
'Expression: {}.toString[key1 + key2] = 1');
722-
723660
expect(function() {
724661
scope.$eval('{}.toString[key1 + key2].foo = 1');
725662
}).toThrowMinErr(
726663
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
727-
'Expression: {}.toString[key1 + key2].foo = 1');
664+
'Expression: {}.toString[key1 + key2].foo = 1');
728665

729666
expect(function() {
730667
scope.$eval('{}.toString["constructor"]["a"] = 1');

0 commit comments

Comments
 (0)