Solution for PortSwigger Academy Lab: DOM XSS in AngularJS expression with angle brackets and double quotes HTML encoded
The following is my documentation on PortSwigger’s Academy labs.
End goal: Perform a XSS attack that executes an AngularJS expression and calls the alert function
This lab is deceitfully easy. We can quickly find the solution for this online, copy and paste the payload, and solve it. Hell, PortSwigger’s own tutorial is only four steps with just 37 words total. It is understanding why our payload works that takes so much time. So, my contented coding cook, let’s do the “recipe” first, then dig deeper into the “why”:
XSS payload that executes an AngularJS expression and calls the alert function, à la Mode
1
servings40
minutes5
minutes☢
kcalAn exquisite XSS attack that bypasses AngularJS angle bracket and quotation security parameters. Best paired with barley wine.
Ingredients
1 (one)
{{$on.constructor('alert(1)')()}}
payload to be placed into the search box.2x scoops of vanilla ice cream.
40 minutes worth of research into the AngularJS framework and JavaScript functions and what is inherited by parent functions.
16 oz brewed coffee, black.
Directions
- Begin by entering a random string into the search box. In this case, “1nfornograpyh” (because, ego).
- Right-click and “inspect element” in our browser. Look for an instance of “ng-” as this informs us that the page is using the AngularJS framework. Observe that our string is enclosed in said ng- directive:
- Place our
{{$on.constructor('alert(1)')()}}
payload into the search box and press the search button. - Eat ice cream with coffee because you are totally an adult.
- Serve!
Pretty simple stuff right? I think z3nsh3ll said it pretty succinctly:
“…there are quite a number of solutions to this lab online and in most cases it’s just someone pasting this particular payload into the search box so I just want to emphasize that this is not hacking. Copying and pasting a payload is not hacking…”1
…hacking is really researching, and finding ways to break code by understanding its syntax. It’s a bit like winning an argument because you are technically right, even if you are arguing in bad faith. You are using the grammar of a code correctly, just not in a way that it was ever “supposed” to be used, and to do that you need to know a whole lot of grammar rules. So yes, you can take the above recipe, pass the lab, and move on. But that would be like memorizing that six multiplied by seven is forty-two without knowing what multiplication is. Sure you can solve this very particular question, but can you solve more like it?
Let’s start at the beginning. We’ll first learn about client-side template injections themselves, learn more about AngularJS, and then piece our payload together.
What are client-side template injections?
Before we can answer that question we first have to know what a client-side template is. A client-side template is a tool that dynamically embeds user-input in a browser without having to rely on a server to reload the page2. Client-side template injections take advantage of this by embedding a malicious template expression, They were first publically discovered by PortSwigger’s research team back in 20163.
AngularJS and how to break it
AngularJS is/was a popular client-side template created by Google that is now defunct and out of date. This does not mean that it has vanished, as many legacy websites have not moved on. The irony about this write-up and its recipe is that, in theory, AngularJS was an ideal template for preventing XSS attacks as it has many safety mechanisms in place. The AngularJS sandbox has sanitizers such as ensureSafeObject function4, ensureSafeMemberName5, and ensureSafeFunction.6 Combined,
“…these sanitizers check for objects like the Function constructor, a DOM element, Object constructor, window object… and checks for a JavaScript property against proto etc.”7
It will then stop executing the expression if its checks are true. Further, the Angular sanitizers
“..safely allow HTML bindings using attributes called ng-bind-html that contain a reference you want to filter. It then takes the input and renders it in an invisible DOM tree and applies white list filtering to the elements and attributes.”8.
The ng-app is a template that contains expressions within double curly brackets. Remember step two of our recipe instructions?
The existence of ng- within a bracket was our indicator that AngularJS was being used as a client-facing template, but we can find ng-app (and ng-scope) in the <body> of the page (they are also very common in the <html> tag):
This is where we start to have fun. We can see AngularJS in action by having it evaluate 1+1 within double curly brackets and placing that in our search box.
When we search for this, instead of seeing “0 search results for ‘1+1′” in the header we get this:
Angular evaluated our input and output “2”. That may not seem exciting at first, but our double curly brakets allowed us to execute AngularJS expressions. Which means that with a little more knowledge of the expressions we can potentially inject harmful code.
Recall that client-side templates allow for dynamic user input. Though the phrase “dynamic user input” would likely get a lot of applause at a TED Talk, it should make a cybersecurity worker sweat. Because it allows our input dynamically, if we can make use of the constructor function then we can break out of the AngularJS sandbox and go for our attack. “But wait,” you say, “you told me that the ensureSafeObject function prevents us from using the constructor function!” and you’d be right. We cannot call the constructor function directly. So {{function(‘alert()'()}} will not work as it will be caught. However, and this is where things get really clever, we can do some research and call a function that has inherited the constructor function’s ability…
Building our XSS payload to break out of AngularJS’s sandbox and call the alert function
The research team at PortSwigger are really mad geniuses in how they got out of the sandbox. It feels like someone asked them to fill out a form in red ink but they used blood instead, and because it was red the form reader didn’t notice the difference. You see, they found functions that were in scope (or commonly, “$scope”, in JavaScript) of the template’s elements and combined those seemingly harmless objects with JavaScript “methods” to do the very same things that a constructor function can. If that sentence did not make much sense to you, have no fear! We’ll go through it.
By “in scope” we just mean that we are looking for objects that are defined and used in the code (z3nsh3ll has a very good explanation on how to find said objects). Below is a quick example on how you might further explore those objects:
This brings us to the first part of our payload: $on. What makes this object useful is that it is an object with function. $on can be swapped out with any other object with function under the element’s scope. z3nsh3ll does this, for example, in his own payload and uses $eval instead. The “constructor function” is so dangerous because it can create and call a created function dynamically. This is why the sanitizer will look for it and catch something like {{function(‘alert()'()}}. So we first get by this by using the non-threatening $on.
Our next part of the payload, .constructor is my favorite part of the trick being pulled. .constructor is a child to function, yet it is not a function itself. It is a JavaScript “method”, but the thing is, it has inherited everything from its parent. Excited yet? You should be, because that means that .construtor is a method that has the constructor function built into it, without it actually being a function! Which means that when it is combined with an in scope object with function like $on, we unlock those dynamic abilities. “$on.constructor”, ends up being the same thing as “function”, but it goes under the radar, allowing us to bypass those pesky framework security parameters. We just attach our ‘alert()’, and we have our final payload.
All we do now is hit “search” and…
We solve the lab!
Potential Impact of an XSS attack using this method
Knowing that AngularJS is a template with built-in sanitizers to prevent XSS attacks (and built by Google, no less) can lull one into a false sense of security. All castles can be sieged. It does not matter if there are walls, towers, moats, and archers, someone will find a way to break through. If the application or website contains sensitive PI or PII a properly planned XSS attack escaping AngularJS’s sandbox could be devasting in both reputation and financially.
Prevention
Thankfully, upgrading from AngularJS (which is defunct) to a newer client-side template which has accounted for this exploit will prevent what we have done today. PortSwigger’s own advice is
“…to prevent client-side template injection vulnerabilities, avoid using untrusted user input to generate templates or expressions. If this is not practical, consider filtering out template expression syntax from user input prior to embedding it within client-side templates.”9
Notes
z3nsh3ll’s quote about cutting and pasting the payload not being hacking resonated with me in this lab. The sentiment may be obvious (and maybe a little bit of gatekeeping), but it is pointing to something important: this payload is truly clever! If you do not take the time to understand why a payload works, you are missing out on so much cool stuff going on in the background and ignoring the sheer amount of knowledge and time that it took to create it. I genuinely got excited when all of the pieces came together and clicked. Yes, grabbing a recipe and going straight to cooking saves time. That makes you a cook, not a chef. The history and culture of the food are important to know because it puts the meal into context. So too is it beneficial to research what went into a payload. This lab brought with it so much learning potential and I feel like we can become better pentesters having done it. We only needed to be curious.
Footnotes
- AngularJS DOM XSS Attack – Understanding $on.constructor ↩︎
- Client-side template injection ↩︎
- XSS without HTML:Client-Side Template Injection with AngularJS ↩︎
- ensureSafeObject function ↩︎
- ensureSafeMemberName ↩︎
- ensureSafeFunction ↩︎
- XSS without HTML:Client-Side Template Injection with AngularJS ↩︎
- XSS without HTML:Client-Side Template Injection with AngularJS ↩︎
- Client-side template injection solutions ↩︎
Share this:
Filed under: Cybersecurity,Pentesting,PortSwigger Academy,XSS attack - @ December 23, 2023 6:10 am
2 thoughts on “Solution for PortSwigger Academy Lab: DOM XSS in AngularJS expression with angle brackets and double quotes HTML encoded”