在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外其他函数的用法。

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

2. nodejs中的命令执行

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

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

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

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

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

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

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

这就有6种思路:

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

2.1 16进制编码

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

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

2.2 unicode编码

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

2.3 加号拼接

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

2.4 模板字符串

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

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

2.5 concat连接

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

2.6 base64编码

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

3. 其他bypass方式

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

3.1 obejct.keys

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

3.2 reflect

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

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

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

这里还有个小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,代码简化后如下:

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

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

过滤掉了buffer,可以换成

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

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

所以基本payload变成

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

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

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

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

感谢我前端大哥semesse的帮助 

参考链接

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

总结

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