Hey Hacker's Hope everyone is doing well…
Let's look at client-side template injection vulnerabilities and how you can exploit them for XSS attacks.
Before going to the CSTI lets learn something about Angular Js AngularJS is a JavaScript framework developed by Google. A framework is basically a set of tools and rules that helps developers build websites or web apps faster and more efficiently. AngularJS is mainly used for creating dynamic, single-page web applications (SPAs) where the content can change without reloading the whole page.
Key Features of AngularJS
- Two-way data binding
- This means the view (what you see on the screen) and the model (the data in the program) are always in sync.
- If the data changes, the view automatically updates, and if the user changes something in the view, the data updates too.
2. MVC Architecture (Model-View-Controller)
- AngularJS separates your app into three parts:
- Model: The data
- View: What the user sees
- Controller: The logic that connects Model and View
3. Directives
- Special HTML attributes like
ng-modelorng-repeatthat add functionality to HTML elements.
4. Dependency Injection
- Makes it easier to manage and reuse code by injecting services or functions where needed.
5. Templates
- You write HTML templates, and AngularJS automatically updates the UI when the data changes.
Think of Angular JS like a smart template engine — it reads your HTML and fills in dynamic content.
One of its coolest features is expressions — little bits of code inside {{ }} (double curly braces). For example:
{{ 1 + 1 }}
AngularJS sees this and displays: 2The Core Problem
Imagine a website has a search bar. You type something, and it shows:
"You searched for: your text here"
Normally, developers protect against attacks by HTML-encoding your input (converting < to < etc.), so even if you type malicious HTML, it shows as plain text and does nothing harmful.
BUT — if the website uses AngularJS, that protection isn't enough.
Because AngularJS reads the page AFTER the HTML is loaded, it will find {{ }} expressions and execute them, even if the HTML encoding is intact. The attacker doesn't need to inject HTML — they just need to inject {{ }}.
But Angular knew expressions could be dangerous, so they built a sandbox — a restricted zone where expressions can run, but can't do truly harmful things like:
- Accessing the browser's
windowobject - Running arbitrary JavaScript functions
- Stealing cookies or redirecting users
Think of the sandbox like a prison yard — inmates can walk around, but can't escape the walls.
The Sandbox Escape — The Clever Hack
The researcher (Gareth Heyes) found a sneaky way to break out of the prison. Here's the logic :
Step 1 — Backdoor a built-in function
JavaScript strings have a built-in method called charAt() — it returns one character at a position. Example:
"hello".charAt(0) // returns "h"The attacker replaced charAt with a different function ([].join) that returns something completely different — the whole string instead of one character.
'a'.constructor.prototype.charAt = [].join;Step 2 — Confuse Angular's parser
Generally , Angular reads and rewrites your expression code before running it.
When Angular sees an expression like {{ x = alert(1) }}, it doesn't run it directly. It first reads it character by character, rewrites it into a safe JavaScript function, then runs that function.
Think of Angular like a document scanner at an airport security checkpoint: The scanner reads your document letter by letter, checks each character, and only lets "safe" documents through.
What charAt normally does
charAt is the tool Angular uses to read your expression one character at a time.
"alert(1)"
charAt(0) → "a"
charAt(1) → "l"
charAt(2) → "e"
...and so onAngular checks each character individually and asks:
"Is this character safe? Is it a letter? A number? A symbol I recognize?"
What happens after the attacker breaks charAt
The attacker replaces charAt with [].join — a function that instead of returning one character, returns the entire string at once.
So now when Angular asks:
"Hey charAt, give me character at position 0"
It gets back:
"alert(1)" — the whole thing at once, not just
"a"
so, Angular considers x=alert(1) is just a variable name (an "identifier"), because its character-checker is now broken and checking the whole string instead of one character at a time.
Real World Analogy 🏦
Imagine a bank's document verification system.
The system checks your ID by reading it one letter at a time and building a profile:
Letter 1: "J" → OK, it's a letter
Letter 2: "o" → OK, it's a letter
Letter 3: "h" → OK, it's a letter
Letter 4: "n" → OK, it's a letter
Result: "John" → Valid name, approved!Now imagine an attacker tampers with the letter-reading machine so instead of reading one letter, it always returns everything at once:
Letter 1 check → machine returns: "John; TRANSFER $1000000 TO ACCOUNT 999"The system was only designed to check one letter — so it just checks the first character "J", sees it's a valid letter, and says ✅ approved!
The rest of the string "; TRANSFER $1000000..." slips through unchecked and gets treated as a real instruction.
Angular has a function called isIdent that checks if something is a valid variable name:
isIdent = function(ch) {
return ('a' <= ch && ch <= 'z' || ...)
}It's designed to receive one character like "a" or "x" and check if it's a letter.
Normally, Angular parses x=alert(1) like this:
Check "x" → it's a letter, so it's a variable name ✅
Check "=" → it's an assignment operator ✅
Check "alert" → DANGER! This is a function call ❌ BLOCKEDAfter the attacker breaks charAt, Angular's parser receives the whole string at once instead of one character:
Check "x=alert(1)" → first character is "x", which is a letter ✅The isIdent check only ever looks at the first character of whatever it receives. Since "x=alert(1)" starts with "x" — a valid letter — it passes! ✅
Angular thinks the entire thing x=alert(1) is just an innocent variable name.
Step 3 — Code gets injected into Angular's generated function
Angular builds a JavaScript function from your expression. With the broken scanner, your alert(1) ends up inside that generated function as real executable code — not as a string, not as data, but as actual running JavaScript.
Final Payload
{{
'a'.constructor.prototype.charAt=[].join;
$eval('x=1} } };alert(1)//');
}}Result: An alert box pops up — meaning the attacker can now run ANY JavaScript on your browser while you visit that site.
What an attacker can do (after breaking security)
- Steal your cookies (login session) → They can log into your account without knowing your password
- Record what you type (keylogging) → They can capture passwords, card details, messages, etc.
- Show fake login forms → Trick you into entering your username/password
- Redirect you to dangerous websites → Send you to phishing or malware pages
- Use your computer for crypto mining → Your browser secretly uses CPU → slows your system
How to stay safe (The Fix)
- Never trust user input in templates
→ Treat
{{ }}like<script>(both can be dangerous) - Update AngularJS → Old versions had this bug → It was fixed in version 1.5.0
- Use newer versions (1.6+) → Angular removed the risky sandbox feature → Safer approach overall
- Use Content Security Policy (CSP) → Adds an extra security layer → Helps block malicious scripts
13v! Bug Bounty Learner | H@ppie H@ck!nG