SQL 注入复习

我将结合 MyBatis 框架 ,从 SQL 注入的原理、多种高危场景(不仅限于 WHERE 条件)、防御策略与最佳实践 三个维度,为你系统、深入、实战化地讲解 SQL 注入问题。内容覆盖你特别强调的:字段名注入、表名注入、ORDER BY 注入、动态 SQL 风险等 ,并给出 可落地的 MyBatis 安全编码规范

一、SQL 注入的本质原理(MyBatis 视角)

🔍 核心问题:

当用户输入被当作"SQL 代码"而非"数据"执行时,就发生了注入。

在 MyBatis 中,这种风险主要源于 错误地使用 ${} 而非 #{}

  • #{property}参数化查询(安全)

    MyBatis 会将其转换为 PreparedStatement 的占位符 ?,由 JDBC 驱动自动转义。

  • ${property}字符串直接替换(危险!)

    MyBatis 会原样拼接到 SQL 中,等同于手动字符串拼接,极易引发注入。

💡 记住:#{} 是盾,${} 是刀------用得好是利器,用不好伤自己。


二、MyBatis 中 SQL 注入的典型场景与示例

场景 1:WHERE 条件注入(最常见)

❌ 危险写法(使用 ${}):
XML 复制代码
<!-- userMapper.xml -->
<select id="findByUsername" resultType="User">
  SELECT * FROM users WHERE username = '${username}'
</select>
java 复制代码
// 调用
userMapper.findByUsername("admin' --");
// 实际执行 SQL:
// SELECT * FROM users WHERE username = 'admin' --'
// → 绕过密码验证,登录 admin 账户!
✅ 安全写法(使用 #{}):
XML 复制代码
<select id="findByUsername" resultType="User">
  SELECT * FROM users WHERE username = #{username}
</select>

→ MyBatis 生成:SELECT * FROM users WHERE username = ?,参数由 PreparedStatement 安全绑定。

场景 2:动态表名 / 字段名注入(高危!常被忽视)

❌ 危险写法:
XML 复制代码
<select id="selectFromTable" resultType="Map">
  SELECT ${columns} FROM ${tableName} WHERE status = #{status}
</select>
java 复制代码
// 攻击调用
mapper.selectFromTable(
  "id, (SELECT password FROM users LIMIT 1) AS pwd", 
  "orders; DROP TABLE users--", 
  1
);
// 实际 SQL:
// SELECT id, (SELECT password FROM users LIMIT 1) AS pwd 
// FROM orders; DROP TABLE users-- WHERE status = 1
// → 数据泄露 + 表被删除!
✅ 安全方案:

原则:绝不允许用户输入直接作为表名/字段名!

  1. 白名单校验(推荐)

    java 复制代码
    public List<Map> safeSelect(String columns, String tableName, int status) {
        Set<String> allowedColumns = Set.of("id", "name", "email");
        Set<String> allowedTables = Set.of("users", "orders");
        
        if (!allowedColumns.containsAll(Arrays.asList(columns.split(","))) ||
            !allowedTables.contains(tableName)) {
            throw new IllegalArgumentException("Invalid column or table");
        }
        return mapper.selectFromTableInternal(columns, tableName, status);
    }

    XML 中仍可用 ${},但确保输入已严格校验。

  2. 避免动态表名

    通过多 Mapper 或泛型设计规避,如 UserMapper, OrderMapper 分开。

场景 3:ORDER BY 注入(盲注高发区)

❌ 危险写法:
XML 复制代码
<select id="listUsers" resultType="User">
  SELECT * FROM users ORDER BY ${sortBy}
</select>
XML 复制代码
// 攻击输入:id ASC,(SELECT CASE WHEN (1=1) THEN 1 ELSE 1/0 END)
// 可用于布尔盲注探测数据库内容
✅ 安全方案:
  • 白名单 + 枚举校验

    java 复制代码
    public enum SortField { ID("id"), NAME("name"), CREATE_TIME("create_time");
        private final String col;
        SortField(String col) { this.col = col; }
        public String getColumn() { return col; }
    }
    
    public List<User> listUsers(SortField field) {
        return mapper.listUsers(field.getColumn()); // 安全
    }
  • 禁止用户传入原始字段名,改为传枚举值或预定义 code。

场景 4:LIMIT / OFFSET 注入(分页场景)

❌ 危险写法:
XML 复制代码
<select id="getPage" resultType="User">
  SELECT * FROM users LIMIT ${offset}, ${limit}
</select>

→ 攻击者可注入 UNION 查询窃取数据。

✅ 安全方案:
  1. 使用 MyBatis-Plus / PageHelper 等分页插件(内部安全处理);

  2. 若手写,对 offset/limit 做类型和范围校验

    java 复制代码
    if (offset < 0 || offset > 10000 || limit <= 0 || limit > 100) {
        throw new IllegalArgumentException("Invalid pagination params");
    }

    ⚠️ 注意:MySQL 支持 LIMIT ?,但 Oracle 不支持,跨数据库需谨慎。

场景 5:动态 WHERE 条件中的列名注入(MyBatis <if> 陷阱)

❌ 危险写法:
XML 复制代码
<select id="search" resultType="User">
  SELECT * FROM users
  <where>
    <if test="field != null and value != null">
      ${field} = #{value}
    </if>
  </where>
</select>

→ 若 field = "1=1; DROP TABLE users--",则注入成功!

✅ 安全写法:
  • field 必须白名单校验(不能来自前端);

  • 或改用固定字段组合:

    XML 复制代码
    <choose>
      <when test="searchByName">name = #{value}</when>
      <when test="searchByEmail">email = #{value}</when>
    </choose>

三、MyBatis 安全开发黄金法则(面试加分项)

原则 说明
永远优先使用 #{} 所有用户输入的"值"必须用 #{}
⚠️ 慎用 ${} 仅用于:静态配置(如 schema 名)、白名单校验后的标识符
🔒 动态标识符必须白名单校验 表名、字段名、排序字段等,禁止直通前端
🧪 开启 MyBatis 日志 开发环境打印实际 SQL,便于审计 log-impl: SLF4J
🛡️ 结合全局防护 WAF(如阿里云 WAF)、数据库权限最小化(应用账号无 DROP 权限)

四、高级防御:审计 + 监控(生产级保障)

即使代码安全,也建议增加纵深防御:

  1. SQL 审计插件:记录所有 DML,便于追溯;
  2. 数据库防火墙 :拦截含 UNIONDROPSLEEP() 等高危关键词的 SQL;
  3. 权限隔离 :应用数据库账号仅授予 SELECT/INSERT/UPDATE/DELETE禁止 DDL 权限

💡 示例:即使攻击者注入了 DROP TABLE,因账号无权限,操作会被拒绝。

五、总结:

"在 MyBatis 项目中,SQL 注入的核心防线是 正确区分 #{}${} 的使用场景

  • 所有用户输入的'值'必须用 #{}
  • 任何动态'标识符'(表名、字段名、排序)必须经过白名单校验;
  • 禁止前端直接传字段名,改为传业务语义参数(如 sortType=NAME)。

此外,通过 SQL 审计插件 + 数据库最小权限 + WAF 构建三层防护,确保即使出现疏漏,也能兜底止损。

安全不是功能,而是架构的一部分。"

相关推荐
爬山算法2 小时前
Redis(115)Redis的性能优化有哪些方法?
数据库·redis·性能优化
小毅&Nora2 小时前
【后端】使用 Easy Rules 构建灵活的业务规则引擎 — Spring Boot 集成实践
java·spring boot·后端
多喝开水少熬夜2 小时前
算法-哈希表和相关练习-java
java·算法·散列表
腾讯云数据库2 小时前
「腾讯云NoSQL」技术之Redis篇:精准围剿rehash时延毛刺实践方案揭秘
数据库
shayudiandian3 小时前
【Java】面向对象编程
java
ZhengEnCi3 小时前
S2B-SQL UPDATE 更新数据完全指南-99%的人忘记WHERE子句,SQL高手却这样写:从基础语法到多表关联的数据修改利器
数据库·sql
xcLeigh3 小时前
融合数据库时代:金仓 “五个一体化” 架构重塑数据管理新范式
数据库
byte轻骑兵3 小时前
数据库迁移革命:金仓KReplay如何用真实负载回放技术缩短3周测试周期
数据库
asom223 小时前
互联网大厂Java求职面试实战:Spring Boot到Kubernetes的技术问答
java·spring boot·kubernetes·oauth2·电商·microservices·面试技巧