XSS With Hoisting

When dealing with JavaScript injection scenarios sometimes we might get into a difficult situation: the target page is not meant to be accessed directly and some of its code is supposed to use some other code in the setup intended.

That leads to some broken script blocks and when the injection context is one of those sections, injection won’t work since the whole executing context is dismissed.

But due to a JavaScript feature named HOISTING it’s still possible to perform a successful XSS PoC (Proof-of-Concept) or attack by fixing the current syntax or by making use of remaining code in the next valid script blocks.

The Problem

A reference to an object not declared or nonexistent makes the JavaScript parser throw a ReferenceError:

Some pages are not intended to be loaded alone with direct browser access. They are part of some other code in which the missing object is properly declared and defined.

Check the following as an example and exercise.

https://brutelogic.com.br/dom/defined.php?value=Brute

That’s a modular page where 2 other pages get called: “undefined1.php” and “undefined2.php” (as commented in source code).

XSS is not possible in “defined” page while it’s possible in “undefined” ones.

1st Case – Missing Object With Property

If we try to inject in “undefined1.php” with just a string breakout and “concatenate” with arithmetic operators (or simply separating commands with semicolon), a ReferenceError is thrown:

The solution lies in JavaScript Hoisting, a feature of the language/interpreter that makes possible to declare variables, functions and classes after its execution.

So here’s the successful payload:

';alert(1);function myObj(){}'

It declares “myObj” as a function. In JavaScript functions are also objects that can have properties and methods. After “myObj” function declaration get moved to the top (hoisting) before the execution chain, it gets the property “myProperty” correctly assigned fixing the syntax.

2nd Case – Missing Object With Method

Again, a regular injection with syntax fixing won’t make the popup appear.

Payload: ');alert(1)('

(it’s possible to save the second semicolon because alert(1)(”) is a valid construct)

By following the previous case we are tempted to employ the same solution but this time we also need to declare the object’s method which is not possible in the same injection:

The solution lies in hijacking an existing global function: by declaring a called undeclared function (CVE case below) or declaring again an existing function called in a valid code section (window object methods for example) we can override it with our own code.

Since the current script block can’t be fixed, hoisting is just used to create that specific function in the injected script block.

Successful payload:

');function atob(){alert(1)}('

The function “atob” is used in the next script block of the page making it possible to be used in our injection. We declare a function with the same name and then it now carries the code we use in declaration (“alert(1)”). When it gets called in the next script block it calls the popup.

CVE-2020-13483 Example

In CVE-2020-13483 regarding a WAF bypass in Bitrix24 CMS, this last hoisting technique is mandatory to achieve XSS. Details are given here including a PoC which can be shortened for better understanding as follows:

Those and some more “Hybrid XSS” cases (input reflected in source code and triggered by JS code in DOM) are handled successfully by our online XSS PoC tool KNOXSS (with 2nd case available in v3.2.0 and up). Check it out!

#hack2learn