这是一个基于nw.js
的开发的工具,有实时预览功能,预览的环境实际上是由预制的一个html渲染出来的,用户写的所有 js 代码也被自动封装成一个 module,通过script
标签的方式引入。最终,用户的代码会在一个沙箱环境中被require
进来,然后执行。
比如
your_code_file.js
的内容例如
|
|
其中define
语句是工具自动生成的。
工具会在之前的处理逻辑里把global
变量以及require
函数重写,导致不能直接调用nodejs中原本的require
函数。处理后的require
函数无法获取到child_process
模块。
在这种情况下,我们需要知道有哪些变量可以使用。通过打印出下列变量,我们就可以对当前环境的状况有一定的了解。
- arguments
- this
- new.target
- window
- parent
- top
- navigator
- location
- name
- global
- self
可以发现,可以用self
、window
、top
等方式获取真正的global
变量,其中我们发现了nodejs原版的require
函数被存在了一个叫做__noderequire
的变量中,直接用它来调用child_process
即可命令执行。
几个版本之后发现用了 nodejs 自带的vm
模块作为沙箱。不过这个已经早已被证实是不安全的。在另一个 nodejs 的沙箱模块vm2
里甚至直接给出了绕过代码。更多的一些姿势可以参考在vm2
模块issue里的一些讨论。
|
|
绕过的思路很简单,找到一个不在沙箱的Context里创建的但是能在沙箱里访问到的变量,从这个变量的属性指针里找出沙箱外的可以执行代码方法,调用即可。在上述代码里,this
的创建其实就是在沙箱外的。this.constructor
为Object() { [native code] }
,this.constructor.constructor
为Function() { [native code] }
。这样,通过this.constructor.constructor
可以获取沙箱外的Function
,即可以用来在沙箱外执行任意代码。其中process.mainModule
里有 nodejs 中的require
函数,可以用来执行任意命令。
解决这个问题需要处理Object.prototype.constructor
到沙箱外面Function
的连接,以及在沙箱内重建输入数据。
在接下来的版本迭代中,似乎 nodejs 被禁止使用了。
假如 nodejs 没有被禁用,程序使用了较为安全的沙箱vm2
,是不是就没有风险了呢?这里还有一个 osx 下面的绕过方式。
我们回顾用户脚本被加载的过程,是将用户的脚本以 script 标签 src 属性的方式加载到页面中。由于 js 文件会被封装成一个 module,里面的代码不会被立即执行。如果用户的脚本文件名称里带有双引号"
,即可闭合 src 属性,导致加载其他的文件,如果直接在这个文件中执行 js 代码,此时的执行环节就在沙箱外,可以直接 RCE。
比如我们创建一个 js 文件名为evil.json" ".js
。再创建一个名为evil.json
的 json 文件。evil.json
里有我们恶意的payload,因为不是js
后缀,所以里面的内容不会被处理。这样经过渲染,页面变成了如下所示。
evil.json
里的恶意代码被执行。
由于在windows环境中无法创建带有双引号的文件名的文件,导致此方法无法在windows下使用。
既然谈到了沙箱,再抄一段禁止eval
的绕过方法吧,来源见参考文献。
eval=undefined
可以用Function(payload)()
绕过。Function.prototype.constructo=rundefined
可以用Object.constructor(payload)()
绕过。Object.getPrototypeOf=undefined
可以用Reflect.construct(Function, [payload])()
绕过。Function=undefined
可以用(function*(){}).constructor(payload)().next()
绕过。
参考文献
https://github.com/patriksimek/vm2/issues/32
https://docs.google.com/presentation/d/1bYFbCtHGimDmqdwE6Um0WZ7O97eWAK-N1qndqrv8niA/pub