预编译为什么可以防御 SQL 注入 ?

科技   2024-11-12 10:21   山西  

预编译最初的目的是提高代码的复用性,因为有很多只有参数值不同的 SQL(完全相同的 SQL 会从缓存里查),比如:

select * from user where id='1'

select * from user where id='2'

这些 SQL 的语法树相同,但每次都要进行重复的编译,很浪费时间。

而预编译可以将 SQL 语句模板化,值的位置用占位符替代,这样数据库就会事先编译好 SQL 语法结构,等真正调用的时候,再传入值执行,省掉了重复建立语法树的时间。

select * from user where id={占位符}

通过抓包来看,SQL 语句先被预编译(Prepare Statement),参数值先用占位符替代。等执行(Execute Statement)的时候,再传入参数。

用户传入的参数不参与语法树的构建,就改不了 SQL 的语法结构,也就避免了注入。

以 MyBatis(半自动化的持久层框架)为例,#{id} 这种格式传参,会先把 SQL 传给数据库进行预编译,等调用的时候,再用参数替换掉占位符,然后执行。


<select id="getUser" resultType="Blog" parameterType=”int”>
         SELECT *
         FROM user
     WHERE id=#{id}
</select>

但有些 SQL 需要使用动态表名和列名,这种时候就不能使用预编译了,需要把 #{id} 换成 ${id},这样参数就会直接参与 SQL 编译,无法防止 SQL 注入,这时候就要手动过滤参数了。

提示:MyBatis 框架的预编译,是 JDBC 中的 PreparedStatement 类在起作用,它的对象包含了编译好的 SQL 语句。

PHP 中使用 MySQL 的预编译功能:

1)定义预编译的 SQL 语句,参数用占位符 ? 表示

$sql = "SELECT * FROM user WHERE id= ? ";

2)创建预处理对象

$mysqli_stmt = $mysqli->prepare($sql);

3)绑定参数

$mysqli_stmt->bind_param('i', $id);

4)绑定结果集

$mysqli_stmt->bind_result($username);

5)执行

$mysqli_stmt->execute();

预编译的局限性

预编译的机制是先编译,再传值,用户传递的参数无法改变 SQL 语法结构,从根本上解决了 SQL 注入的问题。

但并不是所有参数都可以使用预编译,比如动态表名和列名的场景,因为语义分析时,会解析语法树,检查表名和列名是否存在,所以表名和列名不能被占位符替代,也就没法使用预编译。

同理,排序场景的 ASC/DESC 也需要动态传参,不能使用预编译。



信安之路
坚持原创,专注信息安全技术和经验的分享,致力于帮助十万初学者入门信息安全行业。
 最新文章