Skip to main content

SQLI

预编译真的能完美防御SQL注入吗?

在之前面试的时候, 面试官问我, 怎么避免 SQL 注入问题的产生? 当时以为预编译可以解决所有的问题, 就直接说预编译解决问题, 但是实际却不是如此

我们先来看预编译的基本原理, 对于下面这条 SQL 语句

select username, passwd from user where id = ?

如果直接使用拼接的手法进行编写, 编写的 SQL 语句如下

sql = "select username, passwd from user where id = " + num

预编译的写法应该如下所示

sql = "select username, passwd from user where id = ?"
cur.execute(sql, (2))

可以看出,通过提前写好一个sql语句,将需要传入的参数值用符号进行占位,随后预编译,传参执行,这样就允许后续输入进来的内容仅仅是参数值,并不参与到sql语句的构成中。

danger

由此可以看出,预编译是一个防御sql注入的绝佳手段,但真的能绝对防御吗?

刚刚提到,预编译是将sql语句参数化,刚刚的例子中 where语句中的内容是被参数化的。这就是说,预编译仅仅只能防御住可参数化位置的sql注入。那么,对于不可参数化的位置,预编译将没有任何办法。比如下面这些位置:

  • 表名、列名
  • order by、group by
  • limit、join 等等

我们使用 order by 举例, 现在有一个 SQL 语句如下所示:

SELECT * FROM users ORDER BY {user_input};

其中 user_input 是传递过来的参数,例如 id

SELECT * FROM users ORDER BY id;

这个语句是正确的,但是如果user_input输入 id;drop table users --

SELECT * FROM users ORDER BY id;drop table users --

这样就被成功注入了,而这种位置是不可被参数化的,所以是无法通过预编译防御的。

如何防御

所以对SQL注入存在两种情况: 可参数化的,不可参数化的。

  • 对于可参数化没商量,直接预编译解决一切。
  • 而对于不可参数化的,只能通过设置白名单,过滤特殊符号,通过加引号强制转为字符串等方式进行拦截。