Reflected XSS with AngularJS sandbox escape and CSP

5 min read Hard PortSwigger
XSS
Contents

On this page

The lab

here is how portswigger describes it

This lab uses CSP and AngularJS.

To solve the lab, perform a cross-site scripting attack that bypasses CSP, escapes the AngularJS sandbox, and alerts document.cookie.

the last lab was already an angularjs sandbox escape, and this one keeps that exact fight but stacks a content security policy on top, so before the sandbox escape even gets its turn we have to get past csp

The idea#

first is csp, short for content security policy. it is a response header the site sends that tells the browser which places scripts are allowed to come from, and it usually forbids inline scripts entirely. its whole reason to exist is to make xss useless, because even if you manage to inject <script>alert(1)</script>, the browser reads the policy, sees that inline scripts are not allowed, and simply refuses to run it. so our old habit of injecting a script tag is dead on arrival here

but a csp has to allow the site's own scripts to run, and this site allows angularjs that is the crack. once angularjs is loaded and trusted, it will happily scan the page and evaluate any angular directives it finds, running expressions for us. so we do not inject a script at all we inject angular directives, and the framework that csp already trusts becomes our engine xd that is the whole csp bypass in one sentence, borrow the execution that the policy already permits instead of smuggling in your own

second is the same angularjs sandbox from the last lab, which blocks expressions from reaching dangerous things like the window object. so the real task is to combine a csp safe way to fire code, an angular directive, with a sandbox escape

Step 1 - Map the target and watch a plain script get blocked#

first i checked the response headers and found a content security policy header

then viewing the source showed angularjs loaded on the page with the search term reflecting into the html

HTML
<body ng-app ng-csp>

to feel the csp for myself i tried the most basic thing, injecting <script>alert(1)</script> through the search. nothing runs

csp1

this failure is useful, it confirms csp is genuinely stopping script tags, so the way forward cannot be a script at all. it has to be something the policy already let run

step 2 - Fire code the csp approved way with a Directive#

instead of a script we inject an ordinary element carrying an angular directive and the trusted angularjs picks it up and runs the directive's expression for us the element is an input with an event directive on it.

HTML
<input id=x ng-focus=expression>
csp2

ng-focus is angular's directive for running an expression when the element gets focus, so now we just need the input to be focused on its own. we borrow the same autofocus trick from the custom tags lab earlier, we give the input id=x and hang #x on the end of the url, and the browser jumps to and focuses the element with that id as the page loads. an input is focusable by default, so the focus fires, ng-focus runs, and none of it was a script, so csp never objects.

now for the expression inside ng-focus which has to alert document.cookie while never naming window, because that is what the sandbox is filtering for

javascript
$event.composedPath()|orderBy:'(z=alert)(document.cookie)'

$event is the focus event object angular hands to our directive

$event.composedPath() returns the event's path through the page as an array

the very last element of that array is the window object itself. so we now hold window in an array without ever having typed the word.

the | is not a bitwise or here, in angular it means pipe this through a filter, and the filter is orderBy the same evaluator we leaned on last lab. orderBy walks the array and evaluates our argument against each element, and when it reaches that final element, our expression runs with window as its context. that is the sandbox escape, the guard is looking for the word window and we never wrote it, we let composedPath fetch the object and let orderBy run our code against it

the argument (z=alert)(document.cookie) is written in two beats to slip past the sandbox checks on how functions get called. (z=alert) assigns the alert function to a variable z and hands back that function, and then (document.cookie) immediately calls it with the cookie, so the whole thing is just alert(document.cookie)

step 4 - Assemble the payload and deliver#

stitched together

HTML
<input id=x ng-focus=$event.composedPath()|orderBy:'(z=alert)(document.cookie)'>#x

and here is the exploit as it goes on the exploit server, with the search value url encoded and your own lab id filled in.

javascript
<script>
location='https://your-lab-id.web-security-academy.net/?search=%3Cinput%20id=x%20ng-focus=$event.composedPath()|orderBy:%27(z=alert)(document.cookie)%27%3E#x'
</script>
csp3

with this, the lab is solved!