当前位置: 首页 > news >正文

SQL 注入复习

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

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

🔍 核心问题:

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

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

  • #{property}参数化查询(安全)
    MyBatis 会将其转换为 PreparedStatement 的占位符 ?,由 JDBC 驱动自动转义。

  • ${property}字符串直接替换(危险!)
    MyBatis 会原样拼接到 SQL 中,等同于手动字符串拼接,极易引发注入。

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


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

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

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

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

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

❌ 危险写法:
<select id="selectFromTable" resultType="Map">SELECT ${columns} FROM ${tableName} WHERE status = #{status}
</select>
// 攻击调用
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. 白名单校验(推荐)

    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 注入(盲注高发区)

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

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

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

✅ 安全方案:
  1. 使用 MyBatis-Plus / PageHelper 等分页插件(内部安全处理);
  2. 若手写,对 offset/limit 做类型和范围校验
    if (offset < 0 || offset > 10000 || limit <= 0 || limit > 100) {throw new IllegalArgumentException("Invalid pagination params");
    }

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

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

❌ 危险写法:
<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 必须白名单校验(不能来自前端);
  • 或改用固定字段组合:
    <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 构建三层防护,确保即使出现疏漏,也能兜底止损。

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

http://www.dtcms.com/a/593643.html

相关文章:

  • 网站开发工具有哪些wordpress网址跳转
  • Uni微信小程序如何对接百度翻译
  • 蓝牙钥匙 第77次 蓝牙与边缘计算融合:重新定义物联网的智能边界
  • CodexField 如何构建自增长的内容资产生态?
  • 逻辑服务编排的定义变量,将数据和逻辑解耦,配置化实现数据驱动流程!
  • 代码随想录 Q89.跳跃游戏Ⅱ
  • 那个网站是专门做渔具的网站设计与建设难吗
  • Bootstrap4 导航栏
  • ESLint: Expected indentation of * spaces but found *. (style/indent)
  • 前端根据文件后缀名智能识别文件类型的实用函数
  • 文山 网站建设 滇icp成都优化官网推广
  • 线性代数 - 矩阵乘法能换括号,不能换顺序;满足结合律,不满足交换律
  • ScaleRL:掌握大语言模型强化学习的规模化艺术
  • AI MCP体系化开发指南:从诞生背景到技术实现
  • QSS选择器详解:让你的Qt应用界面焕然一新
  • 【底层机制】Android低内存管理机制深度解析
  • 商务网站建设目的电子商务网站开发的预期目标
  • 知识管理的复利效应:从“碎钞机”到“印钞机”的认知升级
  • 2025 年世界职业院校技能大赛汽车制造与维修赛道备赛方案
  • IO 多路复用技术演进与原理深度解析
  • 指纹浏览器字体模拟实践
  • 接口在领域层,实现在基础设施层
  • 【LeetCode刷题】移动零
  • 江苏省建设厅网站公示腾讯企业邮箱注册申请官网
  • 本地部署 Stable Diffusion3.5!cpolar让远程访问很简单!
  • UE_ControllRig交互
  • Swift-snapKit使用
  • Hello-Agents第二章深度解析:智能体的进化之路——从符号逻辑到AI原生
  • 51单片机汇编实现DHT11读取温湿度
  • LiveCharts.Wpf 控件的使用