hex编码绕过通过用0x61646d696e等十六进制字面量替代'admin',避开引号及关键字过滤,mysql原生解析为字符串;需注意偶数长度、数据库兼容性及多变体(如0x、注释嵌入、unhex等),检测应兼顾语法提取与语义校验。

HEX编码在SQL注入中怎么被用来绕过过滤
直接用 0x61646D696E 替代 'admin' 是最常见的绕过方式——当WAF或应用层过滤单引号、双引号甚至关键字时,十六进制字符串本身不包含任何被拦截的字符,且MySQL会原生解析它为对应字节。比如 WHERE username=0x61646D696E 和 WHERE username='admin' 执行效果完全一致。
但注意:这种绕过只对支持十六进制字面量的数据库有效(MySQL、PostgreSQL 支持;SQLite 需开启 hex 模式;SQL Server 用 0x... 表示二进制,不能直接用于字符串比较,需配合 CONVERT)。
- 常见绕过场景:引号被全过滤、
database()等函数名被拦截但字段值仍可控 - 典型payload:
id=1' AND (SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1)=0x7573657273 --+ - HEX字符串必须是偶数长度,否则MySQL报错
Incorrect hexadecimal value
为什么单纯检测 0x[0-9a-fA-F]+ 不够用
正则匹配 0x[0-9a-fA-F]+ 看似能抓出所有HEX编码,但实际漏掉大量变体:
- 大小写混用:
0X61646D696E(MySQL允许大写X) - 中间插入空格或注释:
0x61/**/646D696E、0x61%0a646D696E - 嵌套函数包裹:
UNHEX('61646D696E')、CONV(61646D696E,16,10) - 拼接构造:
CONCAT(0x6164,0x6D696E)
更麻烦的是,0x 前缀本身可能被WAF放行(误判为“只是个普通前缀”),而真正危险的是后面那段可执行的十六进制内容。
输入流里怎么有效识别可疑HEX编码
关键不是“有没有 0x”,而是“这段HEX能不能被数据库当作字符串解释”。建议分两层做检测:
- 第一层(语法层):用正则提取所有疑似HEX字面量,包括
0x[0-9a-fA-F]{2,}、0X[0-9a-fA-F]{2,}、UNHEX\([^)]+\)、CONV\([^)]+\)等模式 - 第二层(语义层):对提取出的HEX串做长度校验(必须偶数)、字符集校验(只能含
0-9a-fA-F)、解码尝试(如bytes.fromhex("61646D696E")不抛异常) - 特别注意:若发现
0x后紧跟27(即0x27→ ASCII单引号),或3B(分号)、3D(等号)、20(空格)等控制字符,基本可判定为恶意构造
真实环境中容易忽略的边界点
很多团队在日志里加了HEX检测规则,却漏掉了这些细节:
- URL参数中已解码的HEX:请求是
?id=0x61646D696E,但中间件(如Nginx、Spring)可能在路由前就完成了URL解码,后端收到的是原始字节流,检测逻辑若只扫原始请求体就会失效 - POST body里的JSON字段:
{"username":"0x61646D696E"}—— 这种字符串在JSON parser之后才暴露,检测必须在反序列化之后、SQL拼接之前介入 - MySQL 8.0+ 的新行为:支持
0x后跟奇数长度(自动补0),但旧版本会报错,所以不能只靠“是否报错”来判断合法性
最稳妥的做法,是在参数绑定前统一做“HEX字符串还原+白名单字符检查”,而不是依赖静态模式匹配。











