11'use strict' ;
22
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+
352var OPERATORS = {
453 'null' :function ( ) { return null ; } ,
554 'true' :function ( ) { return true ; } ,
@@ -36,7 +85,6 @@ var OPERATORS = {
3685 '!' :function ( self , locals , a ) { return ! a ( self , locals ) ; }
3786} ;
3887var ESCAPE = { "n" :"\n" , "f" :"\f" , "r" :"\r" , "t" :"\t" , "v" :"\v" , "'" :"'" , '"' :'"' } ;
39- var $parseMinErr = minErr ( '$parse' ) ;
4088
4189function lex ( text , csp ) {
4290 var tokens = [ ] ,
@@ -204,12 +252,12 @@ function lex(text, csp){
204252 if ( OPERATORS . hasOwnProperty ( ident ) ) {
205253 token . fn = token . json = OPERATORS [ ident ] ;
206254 } else {
207- var getter = getterFn ( ident , csp ) ;
255+ var getter = getterFn ( ident , csp , text ) ;
208256 token . fn = extend ( function ( self , locals ) {
209257 return ( getter ( self , locals ) ) ;
210258 } , {
211259 assign : function ( self , value ) {
212- return setter ( self , ident , value ) ;
260+ return setter ( self , ident , value , text ) ;
213261 }
214262 } ) ;
215263 }
@@ -583,14 +631,14 @@ function parser(text, json, $filter, csp){
583631
584632 function _fieldAccess ( object ) {
585633 var field = expect ( ) . text ;
586- var getter = getterFn ( field , csp ) ;
634+ var getter = getterFn ( field , csp , text ) ;
587635 return extend (
588636 function ( scope , locals , self ) {
589637 return getter ( self || object ( scope , locals ) , locals ) ;
590638 } ,
591639 {
592640 assign :function ( scope , value , locals ) {
593- return setter ( object ( scope , locals ) , field , value ) ;
641+ return setter ( object ( scope , locals ) , field , value , text ) ;
594642 }
595643 }
596644 ) ;
@@ -606,7 +654,7 @@ function parser(text, json, $filter, csp){
606654 v , p ;
607655
608656 if ( ! o ) return undefined ;
609- v = o [ i ] ;
657+ v = ensureSafeObject ( o [ i ] , text ) ;
610658 if ( v && v . then ) {
611659 p = v ;
612660 if ( ! ( '$$v' in v ) ) {
@@ -618,7 +666,9 @@ function parser(text, json, $filter, csp){
618666 return v ;
619667 } , {
620668 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 ;
622672 }
623673 } ) ;
624674 }
@@ -706,18 +756,19 @@ function parser(text, json, $filter, csp){
706756// Parser helper functions
707757//////////////////////////////////////////////////
708758
709- function setter ( obj , path , setValue ) {
710- var element = path . split ( '.' ) ;
759+ function setter ( obj , path , setValue , fullExp ) {
760+ var element = path . split ( '.' ) , key ;
711761 for ( var i = 0 ; element . length > 1 ; i ++ ) {
712- var key = element . shift ( ) ;
762+ key = ensureSafeMemberName ( element . shift ( ) , fullExp ) ;
713763 var propertyObj = obj [ key ] ;
714764 if ( ! propertyObj ) {
715765 propertyObj = { } ;
716766 obj [ key ] = propertyObj ;
717767 }
718768 obj = propertyObj ;
719769 }
720- obj [ element . shift ( ) ] = setValue ;
770+ key = ensureSafeMemberName ( element . shift ( ) , fullExp ) ;
771+ obj [ key ] = setValue ;
721772 return setValue ;
722773}
723774
@@ -728,7 +779,12 @@ var getterFnCache = {};
728779 * - http://jsperf.com/angularjs-parse-getter/4
729780 * - http://jsperf.com/path-evaluation-simplified/7
730781 */
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 ) ;
732788 return function ( scope , locals ) {
733789 var pathVal = ( locals && locals . hasOwnProperty ( key0 ) ) ? locals : scope ,
734790 promise ;
@@ -792,7 +848,7 @@ function cspSafeGetterFn(key0, key1, key2, key3, key4) {
792848 } ;
793849}
794850
795- function getterFn ( path , csp ) {
851+ function getterFn ( path , csp , fullExp ) {
796852 if ( getterFnCache . hasOwnProperty ( path ) ) {
797853 return getterFnCache [ path ] ;
798854 }
@@ -803,12 +859,12 @@ function getterFn(path, csp) {
803859
804860 if ( csp ) {
805861 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 )
807863 : function ( scope , locals ) {
808864 var i = 0 , val ;
809865 do {
810866 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
812868 ) ( scope , locals ) ;
813869
814870 locals = undefined ; // clear after first iteration
@@ -819,6 +875,7 @@ function getterFn(path, csp) {
819875 } else {
820876 var code = 'var l, fn, p;\n' ;
821877 forEach ( pathKeys , function ( key , index ) {
878+ ensureSafeMemberName ( key , fullExp ) ;
822879 code += 'if(s === null || s === undefined) return s;\n' +
823880 'l=s;\n' +
824881 's=' + ( index
0 commit comments