前言
最近看到一个文档有提到关于 SQL 注入的二次注入,来了一点兴趣,遂简单水个文。
原理
二次注入也是 SQL 注入的一种,平时比较难发现。该漏洞的发生主要有两步,第一步是攻击者构造一个恶意的 SQL 语句,SQL 语句存储在数据库中;第二步是将存储在数据库中的恶意 SQL 语句读取并查询,于是就发生了 SQL 二次注入。
整个流程即:插入恶意数据、利用恶意数据。
靶场演示
这里可参考 SQLI-Labs 靶场第 24 号靶场。
1、 打开靶场,用一些 SQL 语句简单测试注入一下皆无果:
简单阅读一下相关登录逻辑代码,发现使用了 mysqli_real_escape_string
转义函数对传入的字符串进行了转义,一些常规的 SQL 注入流程就不容易进行了。
function sqllogin($con1){
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = mysqli_real_escape_string($con1, $_POST["login_password"]);
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
//$sql = "SELECT COUNT(*) FROM users WHERE username='$username' and password='$password'";
$res = mysqli_query($con1, $sql) or die('You tried to be real smart, Try harder!!!! :( ');
$row = mysqli_fetch_row($res);
//print_r($row) ;
if ($row[1]) {
return $row[1];
} else {
return 0;
}
}
2、 于是从注册账户那里去注册一下,这里我们注册的账号为:admin'#
注册成功后并登录刚刚注册的账号,发现可以修改密码:
这里我们重新修改一下当前用户的密码:
接下来,我们使用系统内的 admin
用户,在结合上述刚刚修改的密码,可发现成功登录 admin
用户:
代码分析
既然该题是关于 SQL 二次注入的,且上述涉及到了注册、修改密码的操作,此举成功将原注册用户 admin'#
在修改密码操作时将系统内的 admin
用户“误伤”,从而将 admin
用户密码进行了修改。下面将从用户创建和修改密码的代码分析一下:
1、 login_creat.php
分析:
……
if (isset($_POST['submit']))
{
# Validating the user input........
//$username= $_POST['username'] ;
$username= mysqli_real_escape_string($con1, $_POST['username']) ;
$pass= mysqli_real_escape_string($con1, $_POST['password']);
$re_pass= mysqli_real_escape_string($con1, $_POST['re_password']);
echo "<font size='3' color='#FFFF00'>";
$sql = "select count(*) from users where username='$username'";
$res = mysqli_query($con1, $sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysqli_fetch_row($res);
//print_r($row);
if (!$row[0]==0)
{
?>
<script>alert("The username Already exists, Please choose a different username ")</script>;
<?php
header('refresh:1, url=new_user.php');
}
else
{
if ($pass==$re_pass)
{
# Building up the query........
$sql = "insert into users (username, password) values(\"$username\", \"$pass\")";
mysqli_query($con1, $sql) or die('Error Creating your user account, : '.mysqli_error($con1));
echo "</br>";
……
}
……
在注册用户时,仍然使用了 mysqli_real_escape_string
函数对传入的username
、password
、re_password
字符进行了转义并重新赋值给相关变量,然后判断注册的用户是否已存在数据库中。若数据库中无该用户,且 $pass
和 $re_pass
变量类型一致则进行插入语句操作来进行用户的新增,其语句如下:
$sql = "insert into users (username, password) values(\"$username\", \"$pass\")";
注意这里将数据写入数据库时还是使用原来的数据,也就是将你注册的用户名和密码完完全全插入数据库的。
2、 pass_change.php
分析:
……
if (isset($_POST['submit']))
{
# Validating the user input........
$username= $_SESSION["username"];
$curr_pass= mysqli_real_escape_string($con1, $_POST['current_password']);
$pass= mysqli_real_escape_string($con1, $_POST['password']);
$re_pass= mysqli_real_escape_string($con1, $_POST['re_password']);
if($pass==$re_pass)
{
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
$res = mysqli_query($con1, $sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysqli_affected_rows($con1);
echo '<font size="3" color="#FFFF00">';
echo '<center>';
……
}
……
可以看到,此处仍使用了 mysqli_real_escape_string
函数对提交的 current_password
、password
、re_password
参数进行了转义并赋值给相关变量,但 username
则未进行转义。此时 $pass
和 $re_pass
变量类型一致则进行更新相关用户的密码操作。注意下述的 SQL 语句:
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
此处的 username
则是一开始我们注册的含有恶意字符的用户名,此时,我们若传入该 admin'#
参数,其 SQL 更新语句就会变成:
UPDATE users SET PASSWORD='$pass' where username='admin'# and password='$curr_pass'
显而易见,此处原本修改的是 admin'#
用户的信息,现在却变成了修改 admin
用户的信息。
其用户admin'#
中的 '
号此时则是将原 SQL 语句形成闭合,#
号则将后续的 SQL 语句注释掉不在执行,这就是二次注入达到的效果。于是造成了拿刚刚修改后的相关密码去登录 admin
用户,发现成功登入。
防范
SQL 注入的二次注入,往往需结合代码审计去发现,平时黑盒测试过程时很难发现的。防范主要有以下几点:
1、使用 MySQLi
参数化更新
2、对输入一视同仁,无论输入来自用户还是存储,在进入到 SQL 查询前都对其进行过滤、转义。