SQL 注入防护:参数化查询为什么有效
该文章由AI生成,仅为SEO优化测试,相关内容不保证准确!!!
引言
SQL 注入常年位居 Web 漏洞榜首:攻击者通过构造输入改变 SQL 语义,读取、篡改甚至删除数据。理解注入的原理,才能理解为什么某些“防护”(如过滤关键字)不靠谱,而参数化查询是治本方案。
一、注入是怎么发生的
根源:把用户输入当作 SQL 代码的一部分拼接。典型漏洞代码(PHP 示例):$sql = "SELECT * FROM users WHERE name='" . $_GET['name'] . "'";。攻击者传入 ' OR '1'='1,最终 SQL 变成 SELECT * FROM users WHERE name='' OR '1'='1'——恒真条件,返回全部用户。更危险的玩法:'; DROP TABLE users; -- 通过堆叠查询执行任意语句;利用 UNION SELECT 拖库;利用报错注入带出数据。凡是把输入拼进 SQL 的地方(查询、排序字段、LIMIT 参数、表名)都是注入点。
二、参数化查询为什么有效
参数化查询(预编译语句)把 SQL 结构与数据分离:先发送带占位符的 SQL 模板(SELECT * FROM users WHERE name=?),数据库先解析编译这条语句确定执行计划,再把参数作为纯数据传入。关键在于:参数在解析阶段之后才绑定,无论参数内容是什么,都只会被当作数据值,永远不会改变 SQL 结构。攻击者传入的 ' OR '1'='1 只是一个普通的字符串值,等价于查询一个名字叫这串字符的用户,查不到就返回空。这就是治本:不是过滤危险字符,而是让“数据”与“代码”在机制上隔离。
三、实现方式与常见误区
PHP PDO:$stmt = $pdo->prepare('SELECT * FROM users WHERE name=?'); $stmt->execute([$name]);;Java JDBC 的 PreparedStatement;Python 的 cursor.execute(sql, (val,))。常见误区:① 拼好 SQL 再传给 prepare 不是参数化,注入依旧存在;② 表名、列名、ORDER BY 字段不能用占位符(它们是标识符不是值),需用白名单映射:允许的排序字段写死数组,用户传 key 查表取真实列名;③ LIKE 的通配符 % 与 _ 在参数化后仍是通配符,属业务逻辑问题而非注入,但需按需转义;④ 过滤关键字(替换 select/union)是补丁式防护,绕过方式极多(大小写、注释、编码),不应当作主防线;⑤ ORM(MyBatis 用 #{} 而非 ${}、Hibernate 参数绑定)本质也是参数化,但动态拼接部分仍是风险点。
四、纵深防御
参数化是主防线,再配三层:① 最小权限——应用数据库账号只授予必需表的必需操作,禁止 DROP/FILE 权限,注入后破坏面大幅缩小;② 输入校验——按业务格式校验(手机号 11 位数字、ID 必须整数),既防注入也防脏数据;③ WAF 作为外层兜底,拦截常见注入特征;④ 报错信息不回显 SQL 细节,减少盲注的信息来源。
总结
SQL 注入的根源是数据与代码边界模糊,参数化查询在机制上隔离了两者,是唯一治本方案。辅以最小权限、输入校验、WAF 三层纵深,注入风险可以降到极低。审计存量代码时,凡是字符串拼接 SQL 的地方,都是待修的注入点。