SQL注入(SQL injection)是一种常见的安全漏洞,也是黑客攻击数据库服务器最常用的手段。通过SQL注入,可能导致权限绕过、数据库泄露,甚至服务器被攻陷。
SQL注入案例
以登录为例,通常是获取用户输入的帐号和密码,拼接成SQL,然后到数据库中查询对应的帐户是否存在。例如PHP代码如下:
<?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为:
select * from user where username='admin' and password='123456';
如果可以从数据库中查询到记录,说明admin
用户存在且密码正确。
但是,如果恶意用户输入用户名:admin ’ or 1=1 #
,密码:111111
,则拼接后的SQL为:
select * from user where username='admin' or 1=1 #' and password='111111'
上面SQL中,#
表示注释,后面的and password='111111'
被当作注释了,实际执行的SQL相当于:
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为:
select * from user where username='admin\' or 1=1 #' and password='111111'
转义后,数据库就不会把用户输入的单引号当作用户名的结束,也不会把or
当作指令执行。
2. 检查对用户的输入
在登录的例子中,可以判断用户名中是否全部为数字字符,如果包含其它符号,则提示输入错误。通过这种方法也可以避免SQL的发生。 然而,在更多的场景中,例如发表和回复帖子,需要允许用户输入单引号、双引号等符号,就不能使用这种方法来防范SQL注入。
3. 参数绑定(强烈推荐)
SQL参数绑定,有时候也叫预处理。它的原理是将SQL和SQL中的数据分开发送至数据库服务器,使得数据库服务器不会将数据当作SQL指令。
仍然以登录为例,假设用户输入用户名admin
和密码123456
,参数绑定执行过程如下:
发送SQL到数据库服务器:
select * from user where user=? and password=?
其中,SQL中不包含用户名和密码,相应位置使用占位符
?
代替。发送数据
admin
和123456
至数据库服务器
以PHP为例,使用参数绑定来执行SQL的主要代码为:
<?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语句,并不包含实际输入的用户名和密码。用户名和密码在第二个请求中: