sql注入的本质是用户输入被当作代码执行,核心在于未过滤的输入参与sql字符串拼接,而非数据库类型;典型高危场景包括request.getparameter()直接拼接、mybatis中使用${}动态替换及预编译前已拼入用户数据。

盯住用户输入拼进SQL字符串的位置
SQL注入漏洞的本质不是“用了什么数据库”,而是“用户能控制的输入有没有被当成代码执行”。只要看到 String sql = "SELECT * FROM user WHERE id = " + request.getParameter("id") 这类写法,基本可以判定存在风险——哪怕后面跟了 PreparedStatement 也没用,因为预编译对象的 SQL 字符串本身已经是拼好的。
常见高危组合:
-
request.getParameter()、_GET、_POST等直接参与"... " + xxx + " ..."拼接 -
statement.executeQuery(sql)或statement.execute(sql)调用的是Statement,不是PreparedStatement - MyBatis 中用了
${xxx}(尤其是${tableName}、${sortField}、${column}),而没走#{xxx} - 动态 SQL 的
<if test="xxx != null"></if>块里嵌了${xxx},且xxx来自前端
别被“PreparedStatement”三个字骗了
声明了 PreparedStatement ps = conn.prepareStatement(sql) 不等于安全。真正关键的是:这个 sql 变量是不是静态字符串?还是它本身已经由用户输入拼出来?
典型假安全场景:
-
String condition = "name = '" + request.getParameter("name") + "'"; String sql = "SELECT * FROM user WHERE " + condition; PreparedStatement ps = conn.prepareStatement(sql);→ 危险 -
ps.setString(1, safeValue);但另一处又写了sql = sql.replace("#{table}", tableName);→ 危险 - 调用
ps.executeQuery()前,又做了sql = sql + " AND status = " + statusParam;→ 危险
判断依据只有一个:用户输入是否在预编译前就进入了 SQL 字符串字面量。
一款AI工具,主要用于通过后台进程运行 Codex CLI、Claude Code、OpenCode 或 Pi Coding Agent,实现程序化控制,适合需要提升相关任务效率的用户。
MyBatis 审计重点看 ${} 和 #{} 混用路径
#{} 是安全的占位符,${} 是纯文本替换——等价于手拼 SQL。但问题常藏在调用链里:XML 里写的是 #{name},可传进去的 name 实际是 request.getParameter("sort"),而业务代码里又把它拼进了另一个 ${} 表达式。
实操建议:
- 全局搜索
${,逐个确认变量来源;尤其注意ORDER BY ${sortField}、IN (${ids})、FROM ${tableName} - 检查
<where></where>、<set></set>标签内部是否含${},且该变量未做白名单校验 - 不要轻信“这个字段只允许填 enum”,得看 enum 值是否真来自硬编码,还是从 request 动态反射或 EL 表达式取的
绕过“类型转换”和“过滤函数”的常见手法
很多代码看似加了 Integer.parseInt(id) 或调了 SqlUtils.filter(),但实际仍可绕过:
-
parseInt失败会抛异常,但 catch 后可能返回默认值(如 0)或继续执行后续拼接逻辑 - 过滤函数只处理单引号、分号,却放行了反引号、括号、空格、换行符,攻击者可用
id=1%0aUNION%0aSELECT绕过 - 前端限制了输入长度或正则,但后端没做二次校验,直接信任前端传来的
id - 同一个参数在多个地方被读取:一次走
#{},另一次走${},审计时漏掉后者
真正可靠的防护只有两条:所有用户输入都走参数化查询;非数据部分(表名、列名、排序字段)必须严格白名单校验,不能靠“过滤”或“转义”兜底。










