union注入无法通过正则过滤有效防御,因其依赖数据库语法解析而非字符串匹配,唯一可靠方案是全面禁用字符串拼接、强制使用参数化查询,并对无法参数化的动态位置(如order by字段)实施白名单校验。

UNION 注入不能靠正则匹配防住,这种做法在生产环境里基本等于没防。
你看到的 preg_match("/union|select|from/i", $input) 这类过滤,攻击者几秒就能绕过:用 UNI/<strong>/ON</strong>、%55NION、SeLeCt、甚至把空格换成 %09(制表符)或 //,正则就失效了。数据库根本不在乎大小写或注释符,它只认语法结构。
为什么正则过滤对 UNION 注入无效
正则本质是字符串扫描,而 SQL 解析器执行的是词法 + 语法分析。只要最终拼出合法的 UNION SELECT 结构,不管中间怎么变形,数据库都会执行。常见绕过方式包括:
-
1%20UNION%20SELECT%201,2,3(URL 编码空格) -
1%09UNION%09SELECT%091,2,3(制表符替代空格) -
1/**/UNION/**/SELECT/**/1,2,3(注释符拆分关键字) -
1uNiOn sElEcT 1,2,3(大小写混用) -
1+UNION+SELECT+1,2,3(用加号连接,部分 MySQL 版本仍可解析)
UNION 注入真正依赖的执行条件
它不是靠“有没有 union 字样”触发的,而是依赖两个硬性数据库行为:
- 原始查询和
UNION SELECT的列数必须一致,否则报错ERROR 1222 - 对应列的数据类型需兼容,比如原查询第二列是
VARCHAR,你不能直接填123而不加引号
攻击者正是靠反复发 ORDER BY 1--、UNION SELECT 1--、UNION SELECT 1,2-- 来试出字段数和类型。正则拦不住这种试探,因为它不包含明显敏感词。
唯一有效的防御动作:停用字符串拼接
所有用户输入进 SQL 的地方,必须走参数化查询,不区分字段类型:
- PHP PDO:
$stmt = $pdo->prepare("SELECT * FROM news WHERE id = ?"); $stmt->execute([$id]); - PHP MySQLi:
$stmt = $mysqli->prepare("SELECT name FROM users WHERE id = ?"); $stmt->bind_param("i", $id); $stmt->execute(); - Python psycopg2:
cursor.execute("SELECT * FROM logs WHERE user_id = %s", (user_id,)) - Java JDBC:
PreparedStatement ps = conn.prepareStatement("SELECT * FROM orders WHERE status = ?"); ps.setString(1, status);
注意:ORDER BY、GROUP BY、表名、字段名这些位置无法参数化,必须用白名单校验,比如只允许 ['created_at', 'status', 'id'] 中的值。
真正容易被忽略的点是:哪怕 $id 已经用 (int)$id 或 intval() 处理过,只要 SQL 是拼出来的,1 UNION SELECT ... 这种载荷依然能跑通——因为开头那个 1 是合法数字,后面部分被当作文本原样拼入。防御不在输入端筛字符,而在执行层切断数据与语法的耦合。











