SQL 注入的防御:参数化查询为什么有效
LavenderInk
6 分钟阅读
判断先说:SQL 注入的根本原因,是把"用户数据"和"SQL 指令"混在同一段字符串里拼接。参数化查询(预编译)之所以有效,是让数据库在拿到数据之前就先确定 SQL 结构,用户输入永远只能是"值",永远变不成"命令"。这是防注入的治本之法,不是权宜之计。
拼接字符串为什么会出事
登录 SQL 拼接:
"SELECT * FROM users WHERE name='" + 用户名 + "' AND pwd='" + 密码 + "'"
用户输入用户名: admin' --
拼出来变成:
SELECT * FROM users WHERE name='admin' --' AND pwd='xxx'
-- 后面的条件被注释掉, 绕过密码验证
问题在于,用户输入里的引号、注释符、分号,被数据库解释成了 SQL 语法的一部分,而不是一个普通的值。只要拼接进了引号边界,攻击者就能"逃逸"出数据位置、改写查询意图。拼接越复杂,转义越容易漏,且转义函数本身也可能有边界缺陷,靠"手动转义"防注入是不可靠的。
参数化查询做了什么
参数化查询分两步:先发一条带占位符的 SQL 模板给数据库,数据库解析并编译出执行计划;再把参数值单独传进去。结构与数据走不同的通道。
-- 预编译模板, 结构先定
PREPARE stmt FROM 'SELECT * FROM users WHERE name = ? AND pwd = ?';
-- 参数单独传, 永远只是值
SET @n = 'admin', @p = 'xxx';
EXECUTE stmt USING @n, @p;
数据库在第一步已经确定了"这是一个带两个等于条件的查询",? 的位置只接受值。即便参数里塞了 admin' --,它也只是名为 admin' -- 的字符串去和字段比较,引号不再具有语法意义。用户数据无法改变 SQL 的语法树,注入从源头被堵死。这正是"编译期定结构、运行期填值"带来的本质安全。
各语言的写法要点
// JDBC 用 ? 占位, 不要字符串拼接
String sql = "SELECT id FROM users WHERE name = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, username); // 驱动负责转义与绑定
# Python DB-API 用 %s 占位
cursor.execute("SELECT id FROM users WHERE name = %s", (username,))
// Go database/sql 同样用占位符
db.Query("SELECT id FROM users WHERE name = $1", username)
共同点是:SQL 模板是常量、参数是变量,两者走不同通道传递,永不拼接。无论用哪种语言或框架,只要坚持"模板 + 绑定参数"的写法,注入就没有土壤。ORM 框架默认也是参数化,但要避免它提供的"原生 SQL"或字符串拼接入口被滥用。
参数化不是全部
参数化解决的是"值"的注入。以下场景仍要单独处理,否则仍有漏洞:
- 表名、列名、排序方向不能参数化。这些属于 SQL 结构,占位符只接受值。若需动态指定,必须从白名单里取,绝不能把用户输入直接拼进去。例如排序字段
ORDER BY ? 是不行的,必须校验 sortField 属于已知列集合。
- LIKE 和 IN 的值也要参数化。
'%' + 用户输入 + '%' 里用户输入仍要作为参数传入,特殊字符(如 _、%)在语义层面按需转义,而非靠拼接。IN 列表要用参数展开,不要拼出 IN (1,2,3) 这种字符串。
- 存储过程、动态 SQL 内部若又做字符串拼接,照样存在注入,参数化的保护只在你真正用了绑定参数的地方生效。很多人以为"用了存储过程就安全",其实内部拼接一样中招。
最小可行原则:所有用户输入进 SQL 一律走参数绑定;无法参数化的结构部分严格白名单校验;再配合最小权限账号(应用账号只给必要表的必要操作权限),即便有遗漏,爆炸半径也被限制。这才是层层设防,而非仅靠单点。把"参数化"变成团队的硬编码规范,比任何事后扫描工具都管用。
转义为什么不够
有人会问:不拼接、改成对用户输入做转义(转义引号等)行不行。理论上可行,实践中极不可靠。转义规则依赖字符集和数据库的具体实现,编码稍有不慎(例如宽字节、多重编码)就会绕过;而且一旦某处忘记转义或转义顺序错,漏洞就出现了。参数化从机制上杜绝了"数据变指令"的可能,不依赖开发者每次都正确地转义。把安全寄托在"每个人每次都写对",远不如寄托在"机制本身不允许出错"。安全规范把参数化列为硬性要求,正是这个原因。
浏览 0
点赞 0
评论 0