不能只靠正则匹配select或union,因攻击者使用大小写混淆、注释绕过、url编码、子查询嵌套及合法语法构造(如or 1=1)等方式规避;需用jsqlparser解析为ast,基于结构识别恒真条件、未参数化拼接、越权表引用,并结合上下文策略引擎实现精准防护。

为什么不能只靠正则匹配 SELECT 或 UNION
正则一看到 UNION SELECT 就报警,但攻击者早就不这么写了:用 UnIoN/**/SeLeCt、SELECT%20*%20FROM%20users%20WHERE%201=1、甚至嵌套在子查询里 WHERE id IN (SELECT ...),正则根本抓不住。更危险的是“合法语法+非法意图”,比如 SELECT * FROM users WHERE name = 'admin' OR 1=1 —— 它完全符合 SQL 语法,正则也找不到显式关键字,但 AST 能拆出两个并列的布尔条件,一眼识别出恒真表达式。
用 jsqlparser 解析 SQL 得到 AST 的关键三步
jsqlparser 是 Java 生态最成熟的轻量级 SQL 解析器,不依赖数据库连接,纯内存解析,适合做前置检查。
- 添加 Maven 依赖:
<dependency><groupid>net.sf.jsqlparser</groupid><artifactid>jsqlparser</artifactid><version>4.7</version></dependency> - 调用
CCJSqlParserUtil.parse()获取根节点,它返回的是Statement接口实例(如Select、Update、Delete) - 对节点做类型判断和递归遍历,不要直接 toString() 或正则扫字符串 —— 比如
select.getSelectBody()是PlainSelect,再从中取getWhere()得到Expression树
从 AST 中识别三类高危模式的实际写法
重点不是“有没有 UNION”,而是“这个表达式是否可控”“这个表名是否来自白名单”“这个 WHERE 条件是否可被绕过”。以下检查必须基于 AST 结构,不能靠字符串匹配:
- 检测恒真/恒假条件:遍历
where子句的BinaryExpression,检查左右操作数是否为字面量且运算结果确定(如1=1、'a'='a'),注意排除IS NULL这类正常用法 - 检测未参数化的字符串拼接:若
Column对象的getColumnName()是用户输入(如"name = '" + userInput + "'"),而 AST 中对应位置是StringLiteral节点,就说明没走 PreparedStatement - 检测越权对象引用:拿到所有
Table节点的getName(),比对是否在当前租户/角色允许访问的表白名单内;禁止information_schema、sys等系统库,哪怕语法上合法
AST 检查必须配合上下文才真正有效
单独一个 SQL 字符串的 AST 是静态的,但风险是动态的。比如 SELECT * FROM logs WHERE user_id = ? 在管理员接口里合法,在普通用户接口里就是越权。所以实际落地时:
- 把 AST 分析结果(如是否含子查询、是否含 JOIN、目标表列表)作为输入,喂给策略引擎,策略规则需包含角色、租户、API 路径等上下文字段
- 不要在 AST 层做“修复”,比如自动加
LIMIT—— 这会掩盖语义错误;只做阻断或告警,并附带 AST 节点位置(如 “第3行 WHERE 子句中检测到恒真表达式”) - 性能敏感场景下,先用正则快速筛掉明显恶意字符串(如
; DROP、EXECUTE IMMEDIATE),再进 AST 解析,避免把无效 SQL 也扔给 jsqlparser 报错
AST 不是银弹,它解决不了参数化误用(比如把表名当参数传)、也覆盖不了存储过程内部逻辑。真正的防护线,是 AST 校验 + 白名单控制 + 执行前权限快照,三者缺一不可。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











