Cross-Site Scripting (XSS): The Complete Practical Guide

Client-Side Template Injection (CSTI)

How client-side template injection works: a framework evaluating your input as a template expression, AngularJS sandbox escapes, and escalating to JavaScript.

Published

Client-side template injection happens when a front-end framework treats your input as a template expression and evaluates it in the browser. It is a cousin of DOM-based XSS, but with a twist: you are not injecting HTML into a sink, you are injecting into an expression language that the framework runs. That means you can get execution even when the usual HTML characters are encoded, because angle brackets are not what you need.

When a client framework evaluates markup

Some front-end frameworks scan the DOM for template expressions and evaluate them. If user input is placed into a region the framework then compiles, your input becomes part of a template and is evaluated rather than displayed. The giveaway is that an arithmetic probe comes back computed:

Input:   {{7*7}}
Output:  49

A literal 49 in the page means the framework evaluated your expression. That is the signal you have CSTI, not a plain reflection.

AngularJS {{ }} and sandbox escapes

The textbook case is legacy AngularJS 1.x. Interpolation with {{ }} evaluates expressions, and older versions ran those expressions inside a sandbox meant to keep them away from the real JavaScript scope. The sandbox was repeatedly broken, and in AngularJS 1.6 it was removed entirely because it was never a real security boundary. On a sandboxed version you chain a known escape to reach window, and on an unsandboxed one you call out directly. The exact escape depends on the Angular version, so fingerprint it first, then apply the matching published escape to turn {{ }} evaluation into real JavaScript execution.

Modern framework template interpolation

Modern frameworks do not evaluate arbitrary strings from the DOM the way AngularJS did, which removes the classic CSTI surface. The risk reappears when an app dynamically compiles templates from user input, or feeds untrusted data into a directive, binding, or sink the framework treats as trusted markup. The pattern to watch for is the same: user input reaching a place the framework will evaluate rather than escape. Where the framework parses your input as HTML instead of as an expression, you are back in DOM-based XSS territory.

Detecting it

Probe with an expression that produces an unmistakable result:

{{7*7}}           -> 49 means evaluation
{{7*'7'}}         -> type coercion tells you more about the engine
${7*7}            -> template-literal style engines

Try these in every field that renders back, including ones where HTML is encoded. A computed answer where angle brackets are filtered is a strong CSTI signal, because the engine evaluated your expression without needing any markup.

Escalating to execution

Once you confirm evaluation and the engine, the goal is arbitrary JavaScript. On AngularJS that means the version-appropriate expression (with a sandbox escape if the version still has one) that reaches a function constructor or window and runs your code. From execution, the impact is identical to any other XSS, covered in exploiting XSS.

Why this is XSS even when HTML is encoded

This is the point worth keeping. An app that carefully HTML-encodes <, >, and quotes looks safe against classic XSS, but if your input lands inside a client-side template it is evaluated as an expression, and none of those encoded characters matter. The defense is to never place untrusted input where a template engine will compile it, and to keep frameworks patched past their sandbox-removal versions.

CSTI rewards fingerprinting. Identify the engine, then the escape writes itself. Try a {{7*7}} probe on a free live target.

← Back to Cross-Site Scripting (XSS): The Complete Practical Guide