← 返回题目列表

什么是 SQL 注入?如何防范?

高频 中等 第 7 / 27 题 更新于 2026/07/28
SQL注入网络安全参数化查询预编译

简化版

SQL 注入是攻击者把恶意 SQL 片段混进用户输入里,让它被拼接进后端 SQL 语句并执行,从而绕过登录、窃取/篡改/删除数据库数据。根源是用字符串拼接的方式把用户输入直接塞进 SQL,让「数据」变成了「代码」。防范的核心手段只有一个最有效:参数化查询(预编译 PreparedStatement)——让用户输入永远作为「参数值」,不参与 SQL 语法解析。

详细版

攻击原理:假设登录 SQL 是字符串拼接的:

// ❌ 危险:直接拼接用户输入
String sql = "SELECT * FROM users WHERE name='" + name + "' AND pwd='" + pwd + "'";

攻击者在密码框输入 ' OR '1'='1,拼出来就变成:

SELECT * FROM users WHERE name='admin' AND pwd='' OR '1'='1'

'1'='1' 恒为真,条件被绕过,直接登录成功。更狠的还能用 ; 追加语句('; DROP TABLE users; --)删库、用 UNION 拼接查询窃取其他表数据。

常见类型

  • 联合查询注入(UNION):用 UNION SELECT 拼接查询,把其他表的数据回显出来;
  • 报错注入:故意触发数据库报错,从错误信息里带出数据;
  • 布尔盲注 / 时间盲注:页面不回显数据时,通过「条件真假导致的页面差异」或「SLEEP() 导致的响应延迟」逐位猜出数据。

防范手段

  • 参数化查询 / 预编译(最核心):用 PreparedStatement? 占位符),SQL 结构预先编译,用户输入作为参数传入,不参与语法解析;
  • ORM 框架:MyBatis 用 #{}(预编译)而非 ${}(拼接)、Hibernate/JPA 等默认参数化;
  • 最小权限:数据库账号只给必要权限(别用 root 连业务),即使被注入危害也有限;
  • 输入校验:对类型、格式做白名单校验(辅助手段,不能替代预编译)。

完整版教学

一、SQL 注入的本质:数据被当成了代码

SQL 注入和 XSS 是同一类问题的不同表现——把「用户输入的数据」当成了「可执行的代码」

后端本意是把 namepwd 当作查询条件的值,但字符串拼接让用户输入直接成了 SQL 语句的一部分。攻击者输入的 ' OR '1'='1 里的引号「闭合」了原本的字符串,后面的内容就脱离了「数据」的身份、变成了「SQL 语法」被执行。

所以防御的根本思路是:明确区分「SQL 代码」和「用户数据」,让用户输入永远只能是「数据」,绝不可能变成「代码」。这正是参数化查询做的事。

二、参数化查询为什么能根治

参数化查询(预编译)是防 SQL 注入最有效、最根本的手段,它的原理是「先编译 SQL 结构,再填入数据」:

// ✅ 安全:预编译 + 参数绑定
String sql = "SELECT * FROM users WHERE name=? AND pwd=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, name);   // 用户输入作为「参数值」绑定
ps.setString(2, pwd);

关键在于:

  1. SQL 语句的结构(含 ? 占位符)先被数据库编译好——此时 SQL 的语法结构已经定死;
  2. 之后传入的参数值只作为数据填充到占位符不参与 SQL 语法的解析

所以哪怕用户输入 ' OR '1'='1,它也只会被当成一个普通字符串值去和 name 字段比较(找一个名字真的叫 ' OR '1'='1 的用户),而不会改变 SQL 的逻辑结构。代码和数据被彻底分离,注入无从下手。

三、MyBatis 的 #{}${}:一个天堂一个地狱

用 MyBatis 的项目,这是必考且极易踩坑的点:

  • #{} 是预编译WHERE name = #{name} 会被转成 WHERE name = ? + 参数绑定,安全
  • ${} 是字符串拼接WHERE name = '${name}'直接把值拼进 SQL,等同于危险的字符串拼接,有注入风险

所以原则是:能用 #{} 就绝不用 ${}。只有在需要动态拼接表名、列名、ORDER BY 字段(这些不能用占位符)时才不得不用 ${},此时必须对值做严格的白名单校验,绝不能直接用用户输入。

四、盲注:看不到回显也能拖库

初学者以为「页面不显示数据库内容就没法注入」,其实盲注能在无回显的情况下,一位一位把数据「问」出来:

  • 布尔盲注:构造 AND (SELECT ...)=某值 这样的条件,如果条件为真页面正常、为假页面异常——通过页面的「正常/异常」差异,逐位判断出数据的每个字符;
  • 时间盲注:用 AND IF(条件, SLEEP(5), 0),如果条件为真响应就延迟 5 秒——通过响应时间判断真假。

盲注慢但自动化工具(如 sqlmap)能高效跑。所以只要存在拼接漏洞,不回显也不安全——不能靠「不回显」当防御。

五、纵深防御:预编译之外的加固

参数化查询是主防线,但安全讲究纵深防御,多加几道保险:

  • 最小权限原则:业务数据库账号只授予必要的增删改查权限,别用超级管理员账号连业务。这样即使被注入,攻击者也 DROP 不了库、读不了系统表;
  • 输入校验(白名单):对确定格式的输入(如手机号、id 是数字)做类型/格式校验,拦掉明显异常的输入;
  • WAF(Web 应用防火墙):识别拦截常见注入特征(UNION SELECT' OR)——但只是辅助,能被绕过,不能替代预编译;
  • 错误信息脱敏:别把数据库原始报错直接返回给前端,避免报错注入拿到库表结构信息。

⚠️ 记住优先级:预编译是「根治」,其余是「加固」。别本末倒置地只靠 WAF/输入过滤而用了字符串拼接。

六、常见误区

  • ❌ 以为输入过滤就够了——过滤能被绕过(编码、大小写、注释),根治要靠参数化查询
  • ❌ MyBatis 里用 ${} 拼用户输入——${} 是拼接有注入风险,用 #{}(预编译)。
  • ❌ 以为不回显数据就安全——布尔/时间盲注能无回显地逐位拖库。
  • ❌ 用 root/超管账号连业务库——被注入后危害无限放大,应用最小权限账号。

六、常见误区与追问

考点正确口径
漏洞原因把用户输入拼进 SQL 代码结构
主要防护参数化查询/预编译语句
补充防护最小权限、错误隐藏、输入约束、审计
bad:
SELECT * FROM users WHERE name = '<user input>'

good:
SELECT * FROM users WHERE name = ?
params = [name]

SQL 注入的根因不是“有特殊字符”,而是数据被当成 SQL 语法执行了。

  • 误区:过滤单引号就能防 SQL 注入。 编码、注释、宽字节、数字型注入等都可能绕过黑名单。
  • 误区:ORM 一定不会有注入。 ORM 原生 SQL、字符串拼接、排序字段拼接仍可能注入。
  • 误区:只读接口没有风险。 注入可读敏感数据、探测结构、联合查询,甚至借数据库能力进一步攻击。
  • 追问:预编译为什么有效? SQL 结构先固定,用户输入作为参数绑定,不再被解析为 SQL 语法。
  • 追问:表名和排序字段能参数化吗? 多数数据库参数只能绑定值,字段名应走白名单映射。
  • 追问:最小权限有什么意义? 即使发生注入,也限制攻击者能读写的表和能执行的危险操作。

七、加强记忆

SQL 注入是攻击者把恶意 SQL 拼进用户输入、被拼接执行(绕登录、拖库、删库),根源是字符串拼接让「数据变成代码」。根治靠参数化查询/预编译(PreparedStatement)——SQL 结构先编译、用户输入只作参数不参与语法解析(MyBatis 用 #{} 不用 ${})。纵深防御再加最小权限、输入白名单、WAF、报错脱敏。盲注让「不回显也能拖库」,所以别指望不回显当防御。