文章总结: 本文解析JavaGhostBits攻击原理,指出字符转字节时的低位截断会导致WAF与后端语义分叉,WAF将恶意Unicode判为乱码而后端还原为攻击载荷。该技术可广泛用于文件上传、反序列化、路径穿越及SMTP注入绕过。建议蓝队排查Java解析链,检索强转代码,监控U+0100至U+01FF范围高位字符,并统一解析语义杜绝静默截断。 综合评分: 93 文章分类: 漏洞分析,WEB安全,红队,安全运营,应急响应
hvv 2026 – 今年攻防演练的新变量:WAF 看不见,后端却执行了
原创
MessFeel MessFeel
MessFreeSecurity
2026年7月9日 20:02 北京
在小说阅读器读本章
去阅读
今年 Black Hat Asia 上有份材料,《Cast Attack: A New Threat Posed by Ghost Bits in Java》。没刷屏,没上热搜,群里转发量也不大。
但我看完之后在笔记里写了一行:这东西会成为今年攻防演练里红队工具箱里的常客。
不是某个单点 RCE。没有”一键打穿全网”那种新闻标题。它是一种藏在 Java 生态底层的解析差异攻击面:同一个输入,WAF 看到的是乱码,后端执行出来的是攻击语义。
先说原理,再聊怎么打。
图:Black Hat Asia 2026《Cast Attack: A New Threat Posed by Ghost Bits in Java》原版议题页。
原理:Java 的 char 是 16 位,但很多代码只认低 8 位
Java 的 char 类型是 16 位无符号整数,码点范围 0x0000 到 0xFFFF。但很多 Java 代码在把字符转成字节的时候,做了强转或位运算,只取低 8 位。高位直接被丢弃。
常见的危险写法:
(byte) ch // 只保留低 8 位,高位截断
ch&0xff // 等价操作
outputStream.write(ch) // OutputStream.write(int) 只写低 8 位
DataOutputStream.writeBytes(String) // 每个 char 只取低字节
图:PPT 中总结的 Ghost Bits 典型触发模式,本质都是高位被丢弃、低位被保留。
这意味着什么?任何一个 Unicode 字符,只要它的低 8 位等于某个 ASCII 字符的码点值,经过这些转换之后,就会变成那个 ASCII 字符。
举个例子。. 的 ASCII 码是 0x2E。Unicode 字符 Į(U+012E,带 ogonek 的 I)的二进制是:
0000 0001 0010 1110
^^^^^^^^
低 8 位 = 0x2E = '.'
高位被截断之后,Į 变成了 .。
同理:
| Unicode 字符 | 码点 | 低 8 位 HEX | 截断后变成 | 在攻击里的意义 |
| — | — | — | — | — |
| Į | U+012E | 0x2E | . | 路径分隔、文件扩展名 |
| į | U+012F | 0x2F | / | 路径穿越 |
| Ġ | U+0120 | 0x20 | 空格 | HTTP 协议分段 |
| ħ | U+0127 | 0x27 | ' | SQL 注入单引号 |
| Ŝ | U+015C | 0x5C | \ | Windows 路径分隔 |
| ĥ | U+0125 | 0x25 | % | URL 编码前缀 |
| Ł | U+0141 | 0x41 | A | SQL SELECT 关键字重组 |
| č | U+010D | 0x0D | \r | CRLF 注入 |
| Ċ | U+010A | 0x0A | \n | CRLF 注入 |
规律很简单:要找某个 ASCII 字符的 Ghost Bit 等价物,拿它的码点值加 0x0100,去 Unicode 表里查对应字符。码点在 U+0100 到 U+01FF 之间的拉丁扩展字符,低 8 位刚好覆盖全部 ASCII 范围(0x00 到 0xFF)。
WAF 看到的是 ĮįĮį——”带帽子的字母加斜杠字母”。后端 (byte) ch 之后,看到的是 ../——路径穿越。
这不是编码绕过,是类型收窄制造的语义分叉
传统 WAF 绕过大半是字符串层面的。大小写混写、双写、注释插入、URL 编码、Unicode 编码、双重解码、分块传输。这些打法被规则库覆盖了十几年,蓝队也有肌肉记忆。
Ghost Bits 的本质不是”把 ../ 换个写法”。是让防护设备和执行组件在同一个输入的两个不同表示之间产生分叉。
WAF 工作在 HTTP 层。它看到的是字节流经过 URL 解码之后的字符串。它匹配规则的时候,看到的是 Unicode 字符 ĮįĮį。它的规则库里没有”Į 也是点号”这条规则。放行。
后端 Java 应用拿到这个字符串。它在某个环节调用了 (byte) ch、OutputStream.write(int)、或者某个第三方库底层做了同样的强转。字符被截断,Į 变成 .,į 变成 /。ĮįĮį 变成了 ../。
此时路径穿越已经发生。但 WAF 放行的那个原始请求里,从来没出现过 ../。
这不是编码绕过。是攻击者在字符变成字节的那一瞬间,利用了 Java 类型系统的收窄规则。
图:原版 PPT 将这类问题归到 Traffic Spoofing / WAF Bypass,核心是边界设备和后端组件解释结果不一致。
具体攻击面:PDF 里提到的四类场景
1. 文件上传绕过
Tomcat 的文件上传处理中,DiskFileItem 在写文件的时候,文件名经过了多层处理。Servlet 规范里的 Part.getSubmittedFileName() 返回的是 RFC 5987 编码解析之后的文件名。如果攻击者上传一个文件名包含 Į(U+012E)的 multipart 请求——
WAF 检查扩展名:shell.ĮxĮ。不是 .jsp、不是 .jspx、不是 .war。放行。
Tomcat 在某个内部路径里对文件名做了字符到字节的转换(或者应用自己用 FileOutputStream 写文件的时候传入了含有高位字符的文件名),Į 被截断为 .。最终落盘文件名变成 shell.jsp。
WAF 日志里记录的文件名:shell.ĮxĮ。应用服务器磁盘上的文件名:shell.jsp。两个不同的名字,蓝队如果不把 WAF 原始请求和磁盘文件做 HEX 级别比对,很难发现这个 webshell 是怎么写进去的。
图:文件上传场景里,文件名在检查阶段和落盘阶段可能出现不同语义。
2. Jackson 和 fastjson 的 JSON 字段变形
Jackson 的 charToHex 方法在序列化 JSON 的时候,会把某些 Unicode 字符转换成 \uXXXX 格式。但在反序列化方向,Jackson 解析 \uXXXX 到内部 char 表示之后,如果后续代码把这个 char 写进了字节流——比如拼接成命令、路径、SQL——截断就可能发生。
fastjson 对 Unicode 数字、\u、\x 的解析也有类似链路。攻击者可以在 JSON 字段里嵌入 Ghost Bits 变形后的字符:
{"@type":"com.example.Exploit","cmd":"whoami"}
把 @type 里的 t(0x74)替换成 Ŵ(U+0174)。WAF 的 JSON 注入规则在匹配 @type 关键字时,碰到的字符串是 @Ŵype——不匹配。fastjson 内部在某个字符处理环节做了低位截断,Ŵ 变成 t,@type 被还原,反序列化触发。
这里的关键不是 fastjson 有没有专门处理 Ghost Bits。而是 JSON 库的反序列化链路很长,从字节到字符到对象,每个转换点都可能出现这种截断。攻击者不需要控制整个链路,只需要找到其中一个点。
图:Jackson 案例展示的重点不是单个 payload,而是 WAF 侧字符串和 JSON 解析侧字符串发生分叉。
3. 路径穿越和认证绕过
Openfire CVE-2023-32315 这个案例很典型。原漏洞本质是管理控制台的路径鉴权逻辑存在绕过。传统利用里大家都见过 %u002e 绕过——Unicode dot。绕过方式是把 . 写成 %u002e,让鉴权逻辑不把它当点号处理。
但 Ghost Bits 往底层又推了一层。Jetty 在处理 URL 路径时,内部 Hex 转换逻辑里,某些非标准字符经过 (byte) 强转后,会被折叠成有效的十六进制半字节。什么意思?一个看起来不是 %2e 的字符序列,经过 Jetty 内部的字节转换之后,恰好变成了 %2e,然后被下一阶段的 URL 解码器还原成 .。
鉴权层看到的是”不带点号的路径”,URL 解码后的路径里却出现了 .。鉴权层和路径解析层之间,差了两次转换。
图:Openfire 案例很适合说明路径鉴权和路径规范化之间的语义错位。
Spring CVE-2025-41242 也是同样的思路。Spring MVC 在处理静态资源时,安全检查阶段使用的路径是框架层的规范化结果。如果在安全检查之后、实际读文件之前,Servlet 容器对路径又做了一次解码或规范化——而且这次规范化里触发了 Ghost Bits 截断——那安全检查就是无效的。
蓝队需要记住的不是”某个 CVE 的利用条件”。是一种排查思路:安全检查阶段的输入,和最终执行阶段的输入,是不是经过了不同的转换路径。
图:Spring 静态资源案例里的关键点,是安全检查与最终资源解析之间存在时间差和二次解析。
4. SMTP 注入和邮件头注入
CVE-2025-7962,Jakarta Mail / Angus Mail 的 SMTP 注入。底层原理是邮件库在序列化邮件头的时候,某些字段(发件人显示名、收件人地址、Reply-To)经过了字符到字节的转换。如果上层应用没有过滤高位 Unicode 字符,攻击者在注册邮箱时填入:
[email protected]čĊBcc: [email protected]
č(U+010D)→ 低 8 位是 0x0D = \r。Ċ(U+010A)→ 低 8 位是 0x0A = \n。底层邮件库做了 (byte) ch,čĊ 变成 \r\n。SMTP 协议里,\r\n 是命令分隔符。\r\nBcc: 被 SMTP 服务器解析为新邮件头,攻击者成功插入了一个密送地址。
这封邮件的 SPF、DKIM、DMARC 全过——因为它确实是从企业官方邮件系统发出去的。收件人看到的是可信来源。蓝队如果只查外部钓鱼域名,可能从头到尾没往自家系统邮件上想。
Jira、Confluence 等上层应用底层依赖了 Jakarta Mail。如果它们的用户注册、邮件通知、邀请功能没有对邮箱地址做高位字符过滤——很多系统确实没做,因为”邮件地址校验”通常只查格式,不查 Unicode 范围——这个点就能通。
图:SMTP 注入的危险不只在邮件库本身,还在于它会沿着依赖链扩散到 Jira、Confluence 等业务系统。
为什么蓝队现在就该关注,而不是等第一个案例出来
四条理由。
Java 资产面太大。 OA、SSO、统一认证、API 网关、文件上传服务、DevOps 平台、Jira、Confluence、Jenkins、Keycloak、自研 Spring Boot——攻防演练里常见的核心目标几乎全是 Java 栈。Ghost Bits 不局限于一个框架或一个 CVE,它横跨编码层、容器层、协议层、解析库。任何一个涉及”把外部输入的字符变成字节”的位置,都可能是检查点。
蓝队关注度不足。 这类问题没有一个”10 分 RCE”的标题。它看起来像编码问题、像乱码、像某个库的边界条件。很多防守团队对它没有肌肉记忆——因为没人专门为”char 到 byte 的类型收窄”做过应急响应演练。没有预案,出了事之后排查方向大概率跑偏。
它天然适合做 WAF 绕过。 WAF 规则靠特征匹配。如果有一种方法能让 WAF 看到”不匹配任何特征的 Unicode 字符”,让后端看到”完全匹配攻击特征的 ASCII 字节”,那它就是天生的绕过层。不是绕过一条规则,是绕过一类规则。而且 payload 可以任意变形——同一个 ../ 在 Ghost Bits 里有多组 Unicode 等价表示(U+012E/U+022E/U+032E 等等,低位相同即可)。
它容易造成日志错位。 WAF 日志里是一串 ĮįĮį,应用日志里是解码后的 /admin,容器日志里是规范化后的绝对路径,文件系统上是已经不存在的敏感文件。四层日志,四种表示。如果没有保留原始 HTTP 请求体、没有做多层日志关联,分析员很难还原”这条没命中规则的请求,为什么最后穿越到了 /etc/passwd“。Ghost Bits 制造的请求,WAF 大概率不响,SIEM 大概率不关联,分析员大概率归类为”奇怪的乱码请求”,关闭。
蓝队现在能做的四件事
资产排查。 先把 Java 解析链理一遍。Spring Framework / Boot 的静态资源映射、Jetty / Undertow / Tomcat 的版本和配置、Jackson / fastjson / BCEL / Jakarta Mail / Apache HttpClient 的依赖版本。以及自研代码里涉及 URL decode、文件名处理、Header 处理、SMTP、Redis、路径拼接、字节流写入的部分。不用全量审代码,先圈范围:哪里把外部输入从字符变成了字节,哪里就是 Ghost Bits 的检查点。
代码检索。 让研发和安全一起,在代码仓库里全局搜索这几个 pattern:
(byte) ch
ch & 0xff
0xff & ch
OutputStream.write(int)
DataOutputStream.writeBytes
RandomAccessFile.writeBytes
图:原版 PPT 中的自动发现思路,正好可以转成蓝队代码检索 checklist。
搜出来不等于有漏洞。但要逐个追一个问题:这个转换的输入,能不能被外部用户控制?如果能,转换前有没有做过字符白名单校验?转换后的字节流会不会进入路径、协议、命令、文件名、Header、SMTP 命令?
日志检测。 演练期间重点盯这些日志形态:URL、Query、Header、Cookie、JSON key 里出现 U+0100 到 U+01FF 范围的拉丁扩展字符;文件名里出现 CJK、全角、不可见字符、罕见 Unicode 组合;邮箱注册字段里出现高位 Unicode;WAF 未命中但后端出现路径穿越、异常 404/403/500、静态资源异常访问;邮件系统发出异常收件人或异常内容。核心原则:高位字符出现在路径、协议、文件名、邮箱、Header、JSON key 这些敏感位置时,不要当乱码忽略。同一个请求在 WAF 日志和应用日志里的关键字段值是否一致——如果不一样,查。
统一解析语义。 从工程上收口:安全检查前先做严格、唯一、确定的规范化,拒绝非法编码和非预期字符进入敏感上下文;不要在安全检查后再进行第二次危险解码;字符到字节转换必须显式指定 Charset,对不可映射字符 fail closed——不要静默截断、不要悄悄用 (byte) ch;WAF、网关、应用、容器的解码策略尽可能保持一致。
一句话:不要让 WAF、应用和底层库各说各话。
三个演练剧本
剧本一:老漏洞 + Ghost Bits 绕过 WAF。 目标系统有历史高危漏洞,边界 WAF 已有规则。红队不直接打原始 payload,把关键字符做 Ghost Bits 变形——. 换成 Į,' 换成 ħ,/ 换成 į。WAF 没识别出关键字,后端低位截断后恢复攻击语义。蓝队看 WAF 命中是干净的,真正的证据在后端异常、文件变化、进程行为里。
剧本二:文件上传 Webshell。 上传文件名 shell.ĮxĮ。WAF 检查扩展名——不是 .jsp。Tomcat 或应用代码在写文件时做了 (byte) ch,文件名变成 shell.jsp。蓝队如果不把 WAF 原始请求里的文件名和实际落盘文件名做比对,这个 webshell 的来源就是”未知”。
剧本三:官方邮件社工。 攻击者在目标系统的用户注册页面填入精心构造的邮箱地址。系统发出的”欢迎注册”邮件里被嵌入了 SMTP 注入——密送地址、额外收件人、或者篡改的邮件正文。SPF/DKIM/DMARC 全过。收件人看到企业官方发件人,蓝队查外部钓鱼域名一无所获。真正的入口藏在自家邮件系统里。
Ghost Bits 不是那种能让你在晨会上宣布”紧急修复”的漏洞。没有一个简单粗暴的结论。
但正因为如此,它才适合攻防演练。演练里很多有效攻击,不是靠全新的 0day,而是靠对边界差异的理解:WAF 怎么看、网关怎么解、框架怎么转、容器怎么规范化、底层库怎么写字节。当这些层之间出现理解偏差,攻击者就有了空间。
蓝队要做的,也不是把每一个奇怪字符都当成攻击,而是建立一种意识:所有进入路径、协议、文件名、Header、邮箱、JSON key 的高位字符,都不应该被当成普通乱码。它们可能是攻击者塞进系统里的幽灵比特位。
图:Ghost Bits 的真正威胁是多态性,同一种底层差异可以落到认证绕过、路径穿越、SMTP 注入、请求走私和 WAF 绕过等不同方向。
因为 WAF 看到的不一定是后端执行的。应用检查的不一定是容器解析的。日志记录的不一定是攻击最终生效的。那个看起来像乱码的请求,可能不是乱码——是你跟攻击者之间隔着一层 (byte) ch。
素材来源:Black Hat Asia 2026《Cast Attack: A New Threat Posed by Ghost Bits in Java》,结合安全运营经验整理。
🔚 点个「在看」,或者转发给你们做 Java 的研发,让他搜一下代码里有多少个 (byte) ch。搜出来之后,再让他看看每一个的输入是不是来自 request.getParameter()。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:MessFreeSecurity MessFeel MessFeel《hvv 2026 – 今年攻防演练的新变量:WAF 看不见,后端却执行了》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论