应同时检测0x/0X前缀、UNHEX、CONV、CONCAT等多形式HEX表达式,并校验长度偶数、字符合法、解码无危险控制符,且需适配不同数据库解析差异。

检测0x前缀不能只靠正则匹配
很多WAF或日志规则写成 0x[0-9a-fA-F]+,但真实攻击中这个模式几乎形同虚设。MySQL允许 0X61646D696E(X大写),也接受 0x61/**/646D696E(中间插注释)、0x61%0a646D696E(URL编码换行)。更麻烦的是 UNHEX('61646D696E') 或 CONCAT(0x6164,0x6D696E) ——这些完全不匹配原始正则,却语义等价。
必须同时提取以下模式:
-
0x[0-9a-fA-F]{2,}和0X[0-9a-fA-F]{2,} UNHEX\([^)]+\)CONV\([^)]+\)CONCAT\(0x[0-9a-fA-F]+,\s*0x[0-9a-fA-F]+\)
校验HEX串是否真能被当作字符串解释
光“长得像”没用,关键看它能不能被数据库当字符串执行。比如 0x61646D696E 解码是 "admin",合法;但 0x61646D696 长度为奇数,MySQL会报 Incorrect hexadecimal value,根本不会执行。
语义校验要三步走:
- 长度必须为偶数
- 字符只能是
0-9a-fA-F,含g、_、U等即非法 - 尝试解码:
bytes.fromhex("61646D696E")不抛异常,且解出字节不含控制字符(如0x27单引号、0x3B分号、0x00空字节)
特别警惕 0x27、0x22、0x3D 这类ASCII控制字符出现在HEX串里——基本就是冲着闭合或拼接去的。
注意数据库类型差异导致的误判
同一段 0x61646D696E,在不同数据库行为天差地别:
- MySQL / PostgreSQL:原生解析为字符串,可直接用于
WHERE username = 0x61646D696E - SQLite:默认不识别
0x前缀,需先执行PRAGMA hex=ON,否则整个表达式语法错误 - SQL Server:
0x61646D696E是二进制字面量,不能直接和字符串比较,必须写成CONVERT(VARCHAR, 0x61646D696E)或CAST(0x61646D696E AS VARCHAR)
如果WAF按MySQL逻辑检测,却部署在SQL Server后端,就会漏掉大量真实payload;反之,若按SQL Server逻辑宽松放行,又可能放过MySQL下的有效攻击。
整型注入点硬编码HEX值容易失效
数字型注入点(如 id=1)改用HEX绕过时,常写成 id=-1 UNION SELECT ... WHERE table_schema = 0x6461746162617365。这里 0x6461746162617365 是 "database" 的硬编码,但 database() 函数返回的是运行时库名,可能因多租户、环境切换而变化。
后果是:解码后字符串不匹配,查询结果为空,攻击者得不到数据,但WAF日志里却看不到失败痕迹——因为语法合法、HEX也通过了校验。
真正难防的不是 0x 本身,而是它嵌套在函数调用里,比如 WHERE table_schema = DATABASE() 被过滤后,换成 WHERE table_schema = UNHEX('6461746162617365'),再配合 IF 或子查询动态生成HEX内容,这种组合会让静态检测彻底失效。

















