SQL注入的二阶注入攻击与环境搭建+防范方法

什么是二阶注入?

二阶注入是指完成一次SQL注入攻击需要进行两次SQL注入(即至少两个不一样的SQL语句查询)
比如我在a页面进行了SQL注入,注入的结果在b页面进一步利用,并在b页面再进行注入,即可得到我们想要的结果。

所以,想要完成二阶注入的操作,至少需要在多页面互动

下面以一个注册+修改密码的web漏洞进行二阶注入

环境搭建

环境要求:

  • 连接数据库
  • 需要有一个登录界面,并连上数据库
  • 需要一个注册界面,即本质上是在数据库添加内容
  • 需要一个修改密码的界面,即本质上是修改数据库的内容

登录界面:

代码放在了上一个博客中 https://www.cnblogs.com/Zeker62/p/15216324.html
image

数据库连接代码:

最好出现在每一个文件夹中,这样比较方便:

<?php
$con=mysqli_connect('127.0.0.1','root','root') or die("数据库连接失败!");
mysqli_select_db($con,'lab')or die("数据库连接失败");

?>

注册界面:

register.html

<html>

<head>
    <meta charset="UTF-8">
    <title>Login</title>
    <style>
        #a {
            width: 500px;
            text-align: center;
        }
        
        .b {
            width: 200px;
            height: 30px;
        }
    </style>
</head>

<body>
    <div id=a>
        <h2>Register a account!</h2>
        <form name="form_register" method="POST" action="check_register.php">
            Username:<input type="text" class="b" name="username" /><br> <br> 
			Password:<input type="password" class="b" name="password" /><br>
            <input type="submit" name="Submit" value="Submit" />
            <input type="reset" name="reset" value="Reset" />
        </form>
    </div>
</body>

</html>

register.php

<?php
include('con_database.php');


$username=isset($_POST['username'])?mysqli_escape_string($con,$_POST['username']):'';
$password=isset($_POST['password'])?mysqli_escape_string($con,$_POST['password']):'';

if($username=='' || $password==''){
    echo "<script>alert('请输入账号和密码!')</script>";
    exit;
}
$sql="select * from users where username='$username';";

$query=mysqli_query($con,$sql) or die(mysqli_error($con));

$num=mysqli_fetch_array($query);

if($num){
    echo "该用户名已存在";
    exit;
}

$sql="insert into users (username,passcode) values ('$username','$password')";

mysqli_query($con,$sql) or die("注册失败");

echo "注册成功,请<a href='http://127.0.0.1/web/login/login.html'>登录</a>";
mysqli_close($con);
?>

image

修改密码界面

updatepassword.html

<html>

<head>
    <meta charset="UTF-8">
    <title>Login</title>
    <style>
        #a {
            width: 500px;
            text-align: right;
        }
        
        .b {
            width: 200px;
            height: 30px;
        }
    </style>
</head>

<body>
    <div id=a>
        <h2>Update Your Password!</h2>
        <form name="form_updatepassword" method="POST" action="updatepassword.php">
            Current_password:<input type="text" class="b" name="current_password" /><br> <br> 
			New_password:<input type="text" class="b" name="password" /><br><br>
            <input type="submit" name="Submit" value="Submit" />
            <input type="reset" name="reset" value="Reset" />
        </form>
    </div>
	<a href="http://127.0.0.1/web/login/login.html">返回登录首页</a>
	<br>
	<a href="http://127.0.0.1/web/register/register.html">返回注册首页</a>
</body>

</html>

updatepassword.php

<?php
session_start();
if(isset($_POST['Submit'])){
    include('con_database.php');

    $username=$_SESSION['username'];
    $curr_pass=mysqli_real_escape_string($con,$_POST['current_password']);
    $pass=mysqli_real_escape_string($con,$_POST['password']);
    $sql="update users set passcode='$pass' where username='$username' and passcode='$curr_pass'";

    $res=mysqli_query($con,$sql) or die(mysqli_error($con));

    $row=mysqli_affected_rows($con);


    if($row!=0){
        echo "<script>alert('Change password successfully');history.go(-2)</script>";
        
    }else{
        echo "<script>alert('Change password failed!');history.go(-2)</script>";
    }
}
?>

image

漏洞复现

先创建一个账户:名字叫admin'#
由于在register.php代码中存在mysqli_escape_string()函数,所以我们的单引号会被转义
image
注册成功,在数据库中看下我们刚刚注册的内容,意料之中,账户名就叫admin'#
image
接着,去修改密码:
image
image
修改成功后,查询数据表:
发现admin'#的密码并没有被修改,反而是admin的密码被修改成abc了:
image
于是这样我们都不用知道admin之前的密码,直接就修改了我们想要的密码

漏洞分析

这个二次注入的漏洞原因还是在于updatepassword.php的SQL语句的不合理利用:

$username=$_SESSION['username'];
$curr_pass=mysqli_real_escape_string($con,$_POST['current_password']);
$pass=mysqli_real_escape_string($con,$_POST['password']);
$sql="update users set passcode='$pass' where username='$username' and passcode='$curr_pass'";

尽管对两次输入密码都做了转义,但是并没有对username做转义,这可能基于开发者对第一次已经转义了username的信任。

SQL语句经过调整变成了:update users set passcode='abc' where username='admin'#' and passcode='$curr_pass'
这样一来,后面的passcode验证会被略去,where语句为真,且修改的username为admin

二次注入漏洞都源自于开发者对已经转义的字符不会再造成威胁的刻板认识

漏洞防范

  • 对username进行PHP转义函数的转义——就算之前已经转义过一次,还需要再次转义
  • 使用MySQLi参数化更新,事先编译的PHP代码能够带来高效的防护效果
<?php
session_start();
if(isset($_POST['Submit'])){
    include('con_database.php');
    $username=$_SESSION['username'];
    $curr_pass=$_POST['current_password'];


    if ($curr_pass=='')
        exit("旧密码不能为空");
    $pass=$_POST['password'];
    if($pass='')
        exit("新密码不能为空");
    if($curr_pass==$pass)
        exit("新旧密码相同");
    
    $sql="update users set passcode=? where username=? and passcode=?";

    $stmt=$con->prepare($sql); //对SQL语句进行编译
    if(!$stmt)
        exit("执行错误");
    $stmt->bind_param("sss",$pass,$username,$curr_pass);
    $stmt->execute();

    if($stmt->affected_rows>0){
        echo "<script>alert('当前密码修改成功');history.go(-1);</script>";
    }else{
        echo "<script>alert('当前密码错误');history.go(-1);</script>";
    }
    $stmt->close();
}
mysqli_close($con);
posted @ 2021-09-01 21:10  Zeker62  阅读(395)  评论(0编辑  收藏  举报