Web versions

Client-side model injection | Web Security Academy

In this section, we’ll look at client-side pattern injection vulnerabilities and how you can exploit them for XSS attacks. Although client-side model injection is a generic issue, we will focus on examples from the AngularJS framework as it is the most common. We’ll describe how you can create exploits that escape the AngularJS sandbox and how you can potentially use AngularJS features to circumvent Content Security Policy (CSP).

What is client-side model injection?

Client-side template injection vulnerabilities arise when applications using a client-side template framework dynamically embed user input into web pages. When rendering a page, the framework scans it for pattern expressions and executes any it encounters. An attacker can exploit this by providing a malicious pattern expression that launches a cross-site scripting (XSS) attack.

What is AngularJS Sandbox?

The AngularJS sandbox is a mechanism that prevents access to potentially dangerous objects, such as window Where document, in AngularJS template expressions. It also prevents access to potentially dangerous properties, such as __proto__. Although not considered a security boundary by the AngularJS team, the wider developer community generally thinks otherwise. Although bypassing the sandbox was initially difficult, security researchers have discovered many ways to do it. As a result, it was finally removed from AngularJS in version 1.6. However, many legacy applications still use older versions of AngularJS and therefore may be vulnerable.

How does the AngularJS sandbox work?

The sandbox works by parsing an expression, rewriting the JavaScript, and then using various functions to test whether the rewritten code contains dangerous objects. For example, the ensureSafeObject() The function checks if a given object references itself. It is a way of detecting window object, for example. The Function constructor is detected in much the same way, by checking whether the constructor property references itself.

The ensureSafeMemberName() The function checks each access to the properties of the object and, if it contains dangerous properties such as __proto__ Where __lookupGetter__, the object will be blocked. The ensureSafeFunction()function prevents call(), apply(), bind()Where constructor() to be called.

You can see the sandbox in action for yourself by visiting this violin and setting a breakpoint at line 13275 of the angular.js case. The variable fnString contains your rewritten code, so you can see how AngularJS transforms it.

How does an AngularJS sandbox escape work?

A sandbox evasion consists of tricking the sandbox into thinking that the malicious expression is benign. The best known evasion uses modification charAt() work globally in an expression:

'a'.constructor.prototype.charAt=[].join

When first discovered, AngularJS did not prevent this change. The attack works by overriding the function using the [].join method, which causes the charAt() to return all characters sent to it, rather than just one specific character. Due to the logic of isIdent() function in AngularJS, it compares what it thinks is a single character to multiple characters. Since single characters are always less than multiple characters, the isIdent() The function always returns true, as the following example shows:

isIdent = function(ch) {
return ('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '_' === ch || ch === '$');
}
isIdent('x9=9a9l9e9r9t9(919)')

Once the isIdent() function is fooled, you can inject malicious JavaScript. For example, an expression such as $eval('x=alert(1)') would be allowed because AngularJS treats each character as an identifier. Note that we have to use AngularJS $eval() function because the crushing of the charAt() The function will not take effect until the sandboxed code is executed. This technique would then bypass the sandbox and allow arbitrary JavaScript execution.

Building an advanced AngularJS sandbox escape

So you’ve learned how basic sandbox escaping works, but you may encounter sites that are more restrictive with the characters they allow. For example, a site may prevent you from using double or single quotes. In this situation, you need to use functions like String.fromCharCode() to generate your characters. Although AngularJS prevents access to the String constructor in an expression, you can work around this problem by using a string’s constructor property instead. This obviously requires a string, so to construct an attack like this you need to find a way to create a string without using single or double quotes.

In a standard sandbox escape, you would use $eval() to run your JavaScript payload, but in the workbench below, the $eval() the function is not defined. Fortunately, we can use the orderBy filter instead. The typical syntax of a orderBy the filter is:

[123]|orderBy:'Some string'

Note that the | The operator has a different meaning than in JavaScript. Normally this is bitwise OR operation, but in AngularJS it indicates a filter operation. In the code above we send the array [123] left to orderBy filter on the right. The colons signify an argument to send to the filter, which in this case is a string. The orderBy filter is normally used to sort an object, but it also accepts an expression, which means we can use it to pass a payload.

You should now have all the tools you need to tackle the next workshop.

How does an AngularJS CSP bypass work?

Content Security Policy (CSP) bypasses work similarly to standard sandbox escapes, but typically involve HTML injection. When CSP mode is active in AngularJS, it parses template expressions differently and avoids using the Function constructor. This means that the standard sandbox escape described above will no longer work.

Depending on the specific policy, the CSP will block JavaScript events. However, AngularJS defines its own events which can be used instead. Inside an event, AngularJS defines a special $event object, which simply refers to the browser’s event object. You can use this object to perform a CSP bypass. On Chrome, there is a special property on the $event/event called object path. This property contains an array of objects that cause the event to run. The last property is always the window object, which we can use to perform a sandbox escape. Passing this array to orderBy filter, we can enumerate the array and use the last element (the window object) to perform a global function, such as alert(). The following code demonstrates this:

Notice that the from() The function is used, which allows you to convert an object to an array and call a given function (specified in the second argument) on each element of that array. In this case, we call the alert() function. We cannot call the function directly because the AngularJS sandbox would analyze the code and detect that the window object is used to call a function. By using the from() mask function instead effectively the window sandbox object, allowing us to inject malicious code.

Bypassing a CSP with an AngularJS sandbox escape

This next lab uses a length restriction, so the above vector will not work. In order to exploit the lab, you have to think of different ways to hide the window AngularJS sandbox object. One way to do this is to use the array.map() operate as follows:

[1].map(alert)

map() accepts a function as an argument and will call it for each array element. This will bypass the sandbox because the reference to the alert() the function is used without explicit reference to the window. To solve the lab, try different ways to run alert() without triggering AngularJS window detection.

How to Prevent Client-Side Model Injection Vulnerabilities

To avoid client-side pattern injection vulnerabilities, avoid using untrusted user input to generate patterns or expressions. If this is not practical, consider filtering template expression syntax from user input before integrating it into client-side templates.

Note that HTML encoding is not sufficient to prevent client-side template injection attacks, as frameworks perform HTML decoding of relevant content before locating and executing template expressions.