SQL注入原理及其防范

SQL注入(SQL injection)是一种常见的安全漏洞,也是黑客攻击数据库服务器最常用的手段。通过SQL注入,可能导致权限绕过、数据库泄露,甚至服务器被攻陷。

SQL注入案例

以登录为例,通常是获取用户输入的帐号和密码,拼接成SQL,然后到数据库中查询对应的帐户是否存在。例如PHP代码如下:

1
2
3
4
5
6
7
8
9
<?php
$name = $_POST['name']; // 用户名
$password = $_POST['password']; // 密码
// 拼接SQL
$sql = "select * from user where name='{$name}' and password='{$password}'";
// 到数据库查询
$resutl = mysqli_query($link, $sql);
// 根据查询结果判断密码是否输入正确
// ...

例如,用户名:admin,密码:123456。则SQL为:

1
select * from user where username='admin' and password='123456';

如果可以从数据库中查询到记录,说明admin用户存在且密码正确。
但是,如果恶意用户输入用户名:admin ’ or 1=1 #,密码:111111,则拼接后的SQL为:

1
select * from user where username='admin' or 1=1 #' and password='111111'

上面SQL中,#表示注释,后面的and password='111111'被当作注释了,实际执行的SQL相当于:

1
select * from user where username='admin' or 1=1

SQL中,or 1=1始终成立,意味着,只要user表中有记录,无论恶意用户输入什么密码都能登录成功,这就是SQL注入的危害。

SQL注入原理

上述案例中,恶意用户输入的用户名为admin ’ or 1=1 #,程序的本意是去数据库中查询是否存在用户名为admin ’ or 1=1 #的用户,而数据库处理SQL时却认为其中的单引号'是用户名的结束,or是逻辑运算符,从而导致了SQL注入的发生。

简而言之,SQL注入的原理是,数据库解析SQL时,把数据当作指令执行了

SQL注入防范方法

1. 转义特殊字符(不推荐)

在以下特殊字符前加反斜杠转义:

  • 单引号(’)
  • 双引号(”)
  • 反斜杠(\)
  • NULL

以前面的SQL为例,经过转义后SQL为:

1
select * from user where username='admin\' or 1=1 #' and password='111111'

转义后,数据库就不会把用户输入的单引号当作用户名的结束,也不会把or当作指令执行。

2. 检查对用户的输入

在登录的例子中,可以判断用户名中是否全部为数字字符,如果包含其它符号,则提示输入错误。通过这种方法也可以避免SQL的发生。
然而,在更多的场景中,例如发表和回复帖子,需要允许用户输入单引号、双引号等符号,就不能使用这种方法来防范SQL注入。

3. 参数绑定(强烈推荐)

SQL参数绑定,有时候也叫预处理。它的原理是将SQL和SQL中的数据分开发送至数据库服务器,使得数据库服务器不会将数据当作SQL指令。
仍然以登录为例,假设用户输入用户名admin和密码123456,参数绑定执行过程如下:

  1. 发送SQL到数据库服务器:

    1
    select * from user where user=? and password=?

    其中,SQL中不包含用户名和密码,相应位置使用占位符?代替。

  2. 发送数据admin123456至数据库服务器

以PHP为例,使用参数绑定来执行SQL的主要代码为:

1
2
3
4
5
6
7
8
9
10
<?php
$name = $_POST['name']; // 用户名
$password = $_POST['password']; // 密码

// 准备SQL语句
$stmt = $mysqli->prepare("select * from user where name=? and password=?");
// 指定参数类型(s代表字符串),绑定变量
$stmt->bind_param('ss', $name, $password);
// 执行SQL
$stmt->execute();

SQL注入的原因是数据库服务器把数据当作指令执行了,而参数绑定把数据和指令分开了,这就是参数绑定能防止SQL注入的原理。

深入参数绑定

以下通过WireShark抓包来看看参数绑定的执行过程,在WireShark中输入mysql可以筛选出mysql请求。

输入用户名mao ' or 1=1 #和密码asdfgh以模仿SQL注入,执行代码,然后查看抓包结果如下:

上图中,选中的两行分别是发送SQL语句和执行SQL(发送数据)语句的请求。查看发送SQL的请求:

可以看到,这个请求中只包含带有占位符?的SQL语句,并不包含实际输入的用户名和密码。用户名和密码在第二个请求中: