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

Commit e676d64

Browse files
committed
fix($parse): add quick check for Function constructor in fast path
1 parent 288b531 commit e676d64

File tree

4 files changed

+73
-11
lines changed

4 files changed

+73
-11
lines changed

benchmarks/parsed-expressions-bp/app.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ app.controller('DataController', function($scope, $rootScope) {
7171
date2: new Date(Math.random()*Date.now()),
7272
func: function(){ return star; },
7373
obj: data[i-1],
74-
keys: data[i-1] && (data[i-1].keys || Object.keys(data[i-1]))
74+
keys: data[i-1] && (data[i-1].keys || Object.keys(data[i-1])),
75+
constructor: data[i-1]
7576
});
7677
}
7778

benchmarks/parsed-expressions-bp/main.html

+17
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
<label for="complexPath">Complex Paths</label>
1717
</li>
1818

19+
<li>
20+
<input type="radio" ng-model="expressionType" value="constructorPath" id="constructorPath">
21+
<label for="constructorPath">Constructor Paths</label>
22+
($parse special cases "constructor" for security)
23+
</li>
24+
1925
<li>
2026
<input type="radio" ng-model="expressionType" value="fieldAccess" id="fieldAccess">
2127
<label for="fieldAccess">Field Accessors</label>
@@ -77,6 +83,17 @@
7783
<span bm-pe-watch="row.keys"></span>
7884
</li>
7985

86+
<li ng-switch-when="constructorPath" ng-repeat="(rowIdx, row) in ::data">
87+
<span bm-pe-watch="row.index"></span>
88+
<span bm-pe-watch="row.constructor.index"></span>
89+
<span bm-pe-watch="row.constructor.index"></span>
90+
<span bm-pe-watch="row.constructor.index"></span>
91+
<span bm-pe-watch="row.constructor.constructor.index"></span>
92+
<span bm-pe-watch="row.constructor.constructor.index"></span>
93+
<span bm-pe-watch="row.constructor.constructor.constructor.index"></span>
94+
<span bm-pe-watch="row.constructor.constructor.constructor.index"></span>
95+
</li>
96+
8097
<li ng-switch-when="complexPath" ng-repeat="(rowIdx, row) in ::data">
8198
<span bm-pe-watch="row.index"></span>
8299
<span bm-pe-watch="row.num0"></span>

src/ng/parse.js

+36-10
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,10 @@ function setter(obj, path, setValue, fullExp) {
854854

855855
var getterFnCache = createMap();
856856

857+
function isPossiblyDangerousMemberName(name) {
858+
return name == 'constructor';
859+
}
860+
857861
/**
858862
* Implementation of the "Black Hole" variant from:
859863
* - http://jsperf.com/angularjs-parse-getter/4
@@ -865,33 +869,47 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp) {
865869
ensureSafeMemberName(key2, fullExp);
866870
ensureSafeMemberName(key3, fullExp);
867871
ensureSafeMemberName(key4, fullExp);
872+
var eso = function(o) {
873+
return ensureSafeObject(o, fullExp);
874+
};
875+
var eso0 = isPossiblyDangerousMemberName(key0) ? eso : identity;
876+
var eso1 = isPossiblyDangerousMemberName(key1) ? eso : identity;
877+
var eso2 = isPossiblyDangerousMemberName(key2) ? eso : identity;
878+
var eso3 = isPossiblyDangerousMemberName(key3) ? eso : identity;
879+
var eso4 = isPossiblyDangerousMemberName(key4) ? eso : identity;
868880

869881
return function cspSafeGetter(scope, locals) {
870882
var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope;
871883

872884
if (pathVal == null) return pathVal;
873-
pathVal = pathVal[key0];
885+
pathVal = eso0(pathVal[key0]);
874886

875887
if (!key1) return pathVal;
876888
if (pathVal == null) return undefined;
877-
pathVal = pathVal[key1];
889+
pathVal = eso1(pathVal[key1]);
878890

879891
if (!key2) return pathVal;
880892
if (pathVal == null) return undefined;
881-
pathVal = pathVal[key2];
893+
pathVal = eso2(pathVal[key2]);
882894

883895
if (!key3) return pathVal;
884896
if (pathVal == null) return undefined;
885-
pathVal = pathVal[key3];
897+
pathVal = eso3(pathVal[key3]);
886898

887899
if (!key4) return pathVal;
888900
if (pathVal == null) return undefined;
889-
pathVal = pathVal[key4];
901+
pathVal = eso4(pathVal[key4]);
890902

891903
return pathVal;
892904
};
893905
}
894906

907+
function getterFnWithEnsureSafeObject(fn, fullExpression) {
908+
return function(s, l) {
909+
return fn(s, l, ensureSafeObject, fullExpression);
910+
};
911+
}
912+
895913
function getterFn(path, options, fullExp) {
896914
var fn = getterFnCache[path];
897915

@@ -919,22 +937,30 @@ function getterFn(path, options, fullExp) {
919937
}
920938
} else {
921939
var code = '';
940+
var needsEnsureSafeObject = false;
922941
forEach(pathKeys, function(key, index) {
923942
ensureSafeMemberName(key, fullExp);
924-
code += 'if(s == null) return undefined;\n' +
925-
's='+ (index
943+
var lookupJs = (index
926944
// we simply dereference 's' on any .dot notation
927945
? 's'
928946
// but if we are first then we check locals first, and if so read it first
929-
: '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key + ';\n';
947+
: '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key;
948+
if (isPossiblyDangerousMemberName(key)) {
949+
lookupJs = 'eso(' + lookupJs + ', fe)';
950+
needsEnsureSafeObject = true;
951+
}
952+
code += 'if(s == null) return undefined;\n' +
953+
's=' + lookupJs + ';\n';
930954
});
931955
code += 'return s;';
932956

933957
/* jshint -W054 */
934-
var evaledFnGetter = new Function('s', 'l', code); // s=scope, l=locals
958+
var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject
935959
/* jshint +W054 */
936960
evaledFnGetter.toString = valueFn(code);
937-
961+
if (needsEnsureSafeObject) {
962+
evaledFnGetter = getterFnWithEnsureSafeObject(evaledFnGetter, fullExp);
963+
}
938964
fn = evaledFnGetter;
939965
}
940966

test/ng/parseSpec.js

+18
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,24 @@ describe('parser', function() {
672672

673673
describe('sandboxing', function() {
674674
describe('Function constructor', function() {
675+
it('should not tranverse the Function constructor in the getter', function() {
676+
expect(function() {
677+
scope.$eval('{}.toString.constructor');
678+
}).toThrowMinErr(
679+
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
680+
'Expression: {}.toString.constructor');
681+
682+
});
683+
684+
it('should not allow access to the Function prototype in the getter', function() {
685+
expect(function() {
686+
scope.$eval('toString.constructor.prototype');
687+
}).toThrowMinErr(
688+
'$parse', 'isecfn', 'Referencing Function in Angular expressions is disallowed! ' +
689+
'Expression: toString.constructor.prototype');
690+
691+
});
692+
675693
it('should NOT allow access to Function constructor in getter', function() {
676694

677695
expect(function() {

0 commit comments

Comments
 (0)