1
1
'use strict' ;
2
2
3
+ var $parseMinErr = minErr ( '$parse' ) ;
4
+
5
+ // Sandboxing Angular Expressions
6
+ // ------------------------------
7
+ // Angular expressions are generally considered safe because these expressions only have direct access to $scope and
8
+ // locals. However, one can obtain the ability to execute arbitrary JS code by obtaining a reference to native JS
9
+ // functions such as the Function constructor.
10
+ //
11
+ // As an example, consider the following Angular expression:
12
+ //
13
+ // {}.toString.constructor(alert("evil JS code"))
14
+ //
15
+ // We want to prevent this type of access. For the sake of performance, during the lexing phase we disallow any "dotted"
16
+ // access to any member named "constructor".
17
+ //
18
+ // For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor while evaluating
19
+ // For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor while evaluating
20
+ // the expression, which is a stronger but more expensive test. Since reflective calls are expensive anyway, this is not
21
+ // such a big deal compared to static dereferencing.
22
+ //
23
+ // This sandboxing techniqueue is not perfect and doesn't aim to be. The goal is to prevent exploits against the
24
+ // expression language, but not to prevent exploits that were enabled by exposing sensitive JavaScript or browser apis
25
+ // on Scope. Exposing such objects on a Scope is never a good practice and therefore we are not even trying to protect
26
+ // against interaction with an object explicitly exposed in this way.
27
+ //
28
+ // A developer could foil the name check by aliasing the Function constructor under a different name on the scope.
29
+ //
30
+ // In general, it is not possible to access a Window object from an angular expression unless a window or some DOM
31
+ // object that has a reference to window is published onto a Scope.
32
+
33
+ function ensureSafeMemberName ( name , fullExpression ) {
34
+ if ( name === "constructor" ) {
35
+ throw $parseMinErr ( 'isecfld' ,
36
+ 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}' , fullExpression ) ;
37
+ }
38
+ return name ;
39
+ } ;
40
+
41
+ function ensureSafeObject ( obj , fullExpression ) {
42
+ // nifty check if obj is Function that is fast and works across iframes and other contexts
43
+ if ( obj && obj . constructor === obj ) {
44
+ throw $parseMinErr ( 'isecfn' ,
45
+ 'Referencing Function in Angular expressions is disallowed! Expression: {0}' , fullExpression ) ;
46
+ } else {
47
+ return obj ;
48
+ }
49
+ }
50
+
51
+
3
52
var OPERATORS = {
4
53
'null' :function ( ) { return null ; } ,
5
54
'true' :function ( ) { return true ; } ,
@@ -36,7 +85,6 @@ var OPERATORS = {
36
85
'!' :function ( self , locals , a ) { return ! a ( self , locals ) ; }
37
86
} ;
38
87
var ESCAPE = { "n" :"\n" , "f" :"\f" , "r" :"\r" , "t" :"\t" , "v" :"\v" , "'" :"'" , '"' :'"' } ;
39
- var $parseMinErr = minErr ( '$parse' ) ;
40
88
41
89
function lex ( text , csp ) {
42
90
var tokens = [ ] ,
@@ -204,12 +252,12 @@ function lex(text, csp){
204
252
if ( OPERATORS . hasOwnProperty ( ident ) ) {
205
253
token . fn = token . json = OPERATORS [ ident ] ;
206
254
} else {
207
- var getter = getterFn ( ident , csp ) ;
255
+ var getter = getterFn ( ident , csp , text ) ;
208
256
token . fn = extend ( function ( self , locals ) {
209
257
return ( getter ( self , locals ) ) ;
210
258
} , {
211
259
assign : function ( self , value ) {
212
- return setter ( self , ident , value ) ;
260
+ return setter ( self , ident , value , text ) ;
213
261
}
214
262
} ) ;
215
263
}
@@ -583,14 +631,14 @@ function parser(text, json, $filter, csp){
583
631
584
632
function _fieldAccess ( object ) {
585
633
var field = expect ( ) . text ;
586
- var getter = getterFn ( field , csp ) ;
634
+ var getter = getterFn ( field , csp , text ) ;
587
635
return extend (
588
636
function ( scope , locals , self ) {
589
637
return getter ( self || object ( scope , locals ) , locals ) ;
590
638
} ,
591
639
{
592
640
assign :function ( scope , value , locals ) {
593
- return setter ( object ( scope , locals ) , field , value ) ;
641
+ return setter ( object ( scope , locals ) , field , value , text ) ;
594
642
}
595
643
}
596
644
) ;
@@ -606,7 +654,7 @@ function parser(text, json, $filter, csp){
606
654
v , p ;
607
655
608
656
if ( ! o ) return undefined ;
609
- v = o [ i ] ;
657
+ v = ensureSafeObject ( o [ i ] , text ) ;
610
658
if ( v && v . then ) {
611
659
p = v ;
612
660
if ( ! ( '$$v' in v ) ) {
@@ -618,7 +666,9 @@ function parser(text, json, $filter, csp){
618
666
return v ;
619
667
} , {
620
668
assign :function ( self , value , locals ) {
621
- return obj ( self , locals ) [ indexFn ( self , locals ) ] = value ;
669
+ var key = indexFn ( self , locals ) ;
670
+ // prevent overwriting of Function.constructor which would break ensureSafeObject check
671
+ return ensureSafeObject ( obj ( self , locals ) , text ) [ key ] = value ;
622
672
}
623
673
} ) ;
624
674
}
@@ -706,18 +756,19 @@ function parser(text, json, $filter, csp){
706
756
// Parser helper functions
707
757
//////////////////////////////////////////////////
708
758
709
- function setter ( obj , path , setValue ) {
710
- var element = path . split ( '.' ) ;
759
+ function setter ( obj , path , setValue , fullExp ) {
760
+ var element = path . split ( '.' ) , key ;
711
761
for ( var i = 0 ; element . length > 1 ; i ++ ) {
712
- var key = element . shift ( ) ;
762
+ key = ensureSafeMemberName ( element . shift ( ) , fullExp ) ;
713
763
var propertyObj = obj [ key ] ;
714
764
if ( ! propertyObj ) {
715
765
propertyObj = { } ;
716
766
obj [ key ] = propertyObj ;
717
767
}
718
768
obj = propertyObj ;
719
769
}
720
- obj [ element . shift ( ) ] = setValue ;
770
+ key = ensureSafeMemberName ( element . shift ( ) , fullExp ) ;
771
+ obj [ key ] = setValue ;
721
772
return setValue ;
722
773
}
723
774
@@ -728,7 +779,12 @@ var getterFnCache = {};
728
779
* - http://jsperf.com/angularjs-parse-getter/4
729
780
* - http://jsperf.com/path-evaluation-simplified/7
730
781
*/
731
- function cspSafeGetterFn ( key0 , key1 , key2 , key3 , key4 ) {
782
+ function cspSafeGetterFn ( key0 , key1 , key2 , key3 , key4 , fullExp ) {
783
+ ensureSafeMemberName ( key0 , fullExp ) ;
784
+ ensureSafeMemberName ( key1 , fullExp ) ;
785
+ ensureSafeMemberName ( key2 , fullExp ) ;
786
+ ensureSafeMemberName ( key3 , fullExp ) ;
787
+ ensureSafeMemberName ( key4 , fullExp ) ;
732
788
return function ( scope , locals ) {
733
789
var pathVal = ( locals && locals . hasOwnProperty ( key0 ) ) ? locals : scope ,
734
790
promise ;
@@ -792,7 +848,7 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4) {
792
848
} ;
793
849
}
794
850
795
- function getterFn ( path , csp ) {
851
+ function getterFn ( path , csp , fullExp ) {
796
852
if ( getterFnCache . hasOwnProperty ( path ) ) {
797
853
return getterFnCache [ path ] ;
798
854
}
@@ -803,12 +859,12 @@ function getterFn(path, csp) {
803
859
804
860
if ( csp ) {
805
861
fn = ( pathKeysLength < 6 )
806
- ? cspSafeGetterFn ( pathKeys [ 0 ] , pathKeys [ 1 ] , pathKeys [ 2 ] , pathKeys [ 3 ] , pathKeys [ 4 ] )
862
+ ? cspSafeGetterFn ( pathKeys [ 0 ] , pathKeys [ 1 ] , pathKeys [ 2 ] , pathKeys [ 3 ] , pathKeys [ 4 ] , fullExp )
807
863
: function ( scope , locals ) {
808
864
var i = 0 , val ;
809
865
do {
810
866
val = cspSafeGetterFn (
811
- pathKeys [ i ++ ] , pathKeys [ i ++ ] , pathKeys [ i ++ ] , pathKeys [ i ++ ] , pathKeys [ i ++ ]
867
+ pathKeys [ i ++ ] , pathKeys [ i ++ ] , pathKeys [ i ++ ] , pathKeys [ i ++ ] , pathKeys [ i ++ ] , fullExp
812
868
) ( scope , locals ) ;
813
869
814
870
locals = undefined ; // clear after first iteration
@@ -819,6 +875,7 @@ function getterFn(path, csp) {
819
875
} else {
820
876
var code = 'var l, fn, p;\n' ;
821
877
forEach ( pathKeys , function ( key , index ) {
878
+ ensureSafeMemberName ( key , fullExp ) ;
822
879
code += 'if(s === null || s === undefined) return s;\n' +
823
880
'l=s;\n' +
824
881
's=' + ( index
0 commit comments