# Having some fun with JavaScript hoisting

XSS challenges show how JS hoisting lets injected payloads execute inside function-call parameters and try/catch blocks despite early returns.

- Payout: Research
- Write-up by: Johan Carlsson
- Disclosed: 2023-12-13
- Write-up quality: 7/10
- Original: https://joaxcar.com/blog/2023/12/13/having-some-fun-with-javascript-hoisting/
- Library page: https://bugbountylibrary.com/r/blog%3Ajoaxcar-com-blog-2023-12-13-having-some-fun-with-javascript-hoisting
- Tags: area:web domain:web impact:integrity kind:research platform:research tech:nodejs technique:source-review vuln:xss

**Target:** JavaScript code injection inside function call parameters and try/catch blocks
**Root cause:** JS engines hoist function/var/import/lexical declarations before execution, so injected declarations can satisfy or shadow identifiers and change control flow even when placed after the injection point.
**Steps:**
1. Inject `alert(1));function x(){}//` into `x.y(1,INJECT);` producing `x.y(1,alert(1));function x(){}//)`
2. Function declaration x is hoisted so property access x.y resolves to undefined without ReferenceError
3. Arguments are evaluated before the call, so alert(1) fires before the TypeError on x.y()
4. For nested `x.y.z("INJECTION")` in a module script, inject `");import "data:text/jscript,alert(1)"//` so the hoisted import side effect runs first
5. For the try/catch challenge, break out of the comment with a newline and inject `let config;` to shadow the outer config, forcing a ReferenceError into catch
6. Craft a payload valid in both try and catch blocks, e.g. `"-alert()-"%E2%80%A8let%20config` using U+2028 line separator
**How found:** Author reasoned about JS two-pass parsing: since declarations are hoisted before execution and call arguments evaluate before the callee check, an injected function declaration can make an otherwise-crashing expression still evaluate its arguments.
**Exploitation:** Attacker controls a reflected injection point inside generated JavaScript; by supplying syntactically valid payloads that leverage hoisting, arbitrary JS (alert(1)) executes in the victim's browser despite surrounding code structure.
**Insight:** When injecting into a function-call argument list where the callee is undefined, append a hoisted function declaration for the callee so parsing succeeds and arguments still evaluate; in module scripts, hoisted static imports run their side effects before the rest of the script.

## Proof of concept (copied exactly from the original report)

**Injection solving undeclared function call**

```
alert(1));function x(){}//

which will create this piece of JavaScript when injected

x.y(1,alert(1));function x(){}//)
```

**Module import hoisting payload**

```
x.y.z("");import "data:text/jscript,alert(1)"//")
```

**try-catch hoisting payload**

```
"-alert()-`\nlet config;x=/`-"/
```

