Published: 14 October 2019 at 13:04 UTC
Updated: 08 September 2020 at 12:15 UTC
Often when you are testing websites you might encounter a length restriction on the input you are testing, that's why it's important to reduce the length of your vector as much as possible and of course it's really fun too.
When I did my XSS magic tricks talk at Allstars I presented a technique to bypass CSP using AngularJS in 63 characters:
<input id=x ng-focus=$event.path|orderBy:'CSS&&[1].map(alert)'>
It works by using the path property in Chrome which is an array of DOM objects which also contains the window object as the last element. The pipe character in AngularJS signifies a filter operation so the path array is sent to the orderBy filter and this accepts an expression. Then when the expression is executed the context in which it executes in the current element of the array. I then use the CSS
property to detect when the array element is currently using the last element which is window. Then I use the map function to execute the alert
function because the sandbox will detect window if calling alert
as a normal function call. The vector is below:
I challenged the audience to reduce it further and Erlend Oftedal managed to do it by replacing the CSS
property with "x" which is the ID of the injected input element. This works because "x" will be a global property of window and thus can be used to detect the window object. See below:
<input id=x ng-focus=$event.path|orderBy:'x&&[1].map(alert)'>
I posted this vector on Slackers with a challenge to reduce it further. Taking up the challenge myself, I thought of ways to call alert
without triggering AngularJS window detection. I came up with using an array and the pop
method:
<input id=x ng-focus=$event.path|orderBy:'x&&[alert].pop()(1)'>
This payload is the same length as the original. But wait - AngularJS allows you to call undefined functions silently without throwing exceptions. So I could reduce the payload by removing the &&
and property check:
<input id=x ng-focus=$event.path|orderBy:'[alert].pop()(1)'>
Still determined to reduce it further I tried using an array without the pop
by referencing the first element and calling it:
<input id=x ng-focus=$event.path|orderBy:'[alert][0](1)'>
But this results in an illegal operation as the calling object is not window and JavaScript only lets us call alert
on window. So I needed a way of calling alert
as a direct call and maintaining the reference to window whilst still avoiding AngularJS's window check. Finally I came up with this simple but elegant method - using an assignment evades AngularJS's window object detection:
<input id=x ng-focus=$event.path|orderBy:'(y=alert)(1)'>