PHP mysqli_multi_query 执行多条语句及 web端回显的问题

问题

问题启发于 sqli-labs的42关,堆叠注入。sqli-labs搭建推荐直接使用Docker。
docker run -dt --name sqli-lab -p [PORT]:80 acgpiano/sqli-labs:latest
其中第42关是个堆叠注入的问题。通过post方式来传递参数。但是这关的username输入有mysqli_real_escape_string函数管着,所以注入点在password。最终构造的sql注入语句为
login_user=admin1&login_password=admin1';create table tesst(id INT,name varchar(100)) %23; &mysubmit=Login
当时,我尝试的时候注入语句没有使用create table语句,而是使用的select sleep(5)。既然是堆叠注入就是都当作sql语句来执行,这个语句应该也是可以执行的,页面应该等待5秒再返回才对。但是,事实并不是这样,页面直接返回了。接下来就是找出为什么是这样的结果。

解决

数据库直接执行注入的语句

使用 Navicat 连接数据库,然后直接执行注入的语句。如下图所示。

语句都成功执行了。说明多条语句执行以及sleep()对于我这个数据库来讲都是没有问题的。

然后就猜测是不是注入语句被什么给过滤处理了。

查看数据库的查询记录,并在终端中直接执行注入语句

上一个博客讲了mysql配置记录查询语句的功能以及查看执行语句历史的方法,这里就不赘述了。直接查看历史记录。

上图中红色圈起来的就是从web界面注入的语句。可以看到输入的语句没有被过滤什么的,成功到了数据库中被执行。但是sleep(5)并没有在web端体现出来,为什么呢?

下面尝试在终端中直接执行注入的sql语句。

上图中的sql语句中最后的单引号'是与PHP程序中原查询拼接导致的。原PHP程序中的查询是这样写的$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";。在PHP程序的查询中,这个单引号'会被注释符#注释掉,所以没有影响。但是在终端中,需要分号;作为语句结束,所以可以看到在终端中执行的截图中,我有输入了一个分号;来表示语句的结束。

上面的过程验证了,注入的sql语句是可以到达数据库中并被成功执行的。

对比源代码分析。

对比代码

sqli-labs对应的源代码地址为 https://github.com/Audi-1/sqli-labs/blob/master/Less-42/login.php 。其中的关键查询语句如下。

 $sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
   if (@mysqli_multi_query($con1, $sql))
   {
        /* store first result set */
      if($result = @mysqli_store_result($con1))
      {
	 if($row = @mysqli_fetch_row($result))
	 {
	    if ($row[1])
	    {
	       return $row[1];
	    }
	    else
	    {
	       return 0;
	    }
	 }
      }
      
      else 
      {
	echo '<font size="5" color= "#FFFF00">';
	print_r(mysqli_error($con1));
	echo "</font>";  
      }
   }

为了便于对比,我将sqli-labs中的源码简化成了下面这样的,并将其命名为test2.php

<?php
$con=mysqli_connect("localhost","test","test","test");

// Check connection
if (mysqli_connect_errno($con))
{
	echo "Failed to connect to MySQL: " . mysqli_connect_error();
}

$sql = "SELECT * FROM users WHERE id=1;";
//$sql .= "SELECT * FROM users where id=2";
$sql .= "select sleep(20);";
$sql .= "select sleep(30);";

/* execute multi query */
if (mysqli_multi_query($con, $sql))
{
    /* store first result set */
    if($result = mysqli_store_result($con))
    {
        if($row = mysqli_fetch_row($result))
        {
            printf("%s\n",$row[0]);
	    printf("==================\n");
        }
    }
}

?>

然后从菜鸟教程找了一份同样使用mysqli_multi_query()的例子,并命名为test1.php来做对比,内容如下所示。

<?php
$con=mysqli_connect("localhost","test","test","test");
// 检测链接
if (mysqli_connect_errno($con))
{
    echo "连接到 MySQL 失败: " . mysqli_connect_error();
}
 
$sql = "SELECT * FROM users where id=1;";
//$sql .= "SELECT * FROM users where id=2";
$sql .= "select sleep(20);";
$sql .= "select sleep(30);";
 
// 执行多个 SQL 语句
if (mysqli_multi_query($con,$sql))
{
    do
    {
        // 存储第一个结果集
        if ($result=mysqli_store_result($con))
        {
            while ($row=mysqli_fetch_row($result))
            {
                printf("%s\n",$row[0]);
		printf("==================\n");
            }
            mysqli_free_result($result);
        }
    }
    while (mysqli_next_result($con));
}
 
mysqli_close($con);
?>

搭建测试环境

测试机 ubuntu 18.04
测试这种小脚本,也没必要搭建web环境,直接安装好php的环境,在终端中执行就行了。安装phpphp-mysql的命令如下。

sudo apt install php7.2-cli
sudo apt install php-mysql

切换到包含test1.php的目录下,脚本执行方式为php test1.php

源代码分析

  1. 上面test2.php中的代码,虽然有多条sql查询,但是只取了一次结果。而test1.php则是通过循环依次取多条sql语句的结果。
  2. 上面两份代码中都分别包含3条查询。第一条是正常的查询,第二和第三条分别是睡眠20和30秒

执行结果

先执行php test1.php,结果如下:

可以看到,依次输出了对应的结果,查询数据库的历史记录,也可以看到都依次成功执行了,没啥问题。

然后执行php test2.php,结果如下:

然后再查询数据库的执行记录,如下图所示

虽然这个程序只取了第一条语句的结果,但是所有三条查询均执行了。而且,在程序刚执行第二个查询,也就是select sleep(20)的时候,迅速查看数据库的历史记录如下

可以看到第三条查询sleep(30)还未执行。现在基本可以得出结论了。

结论

mysqli_multi_query中的多条sql查询(由分号'分隔),无论是否取结果,取几个结果,都将提交给数据库执行,并且是顺序执行,执行完一条再执行下一条。

这个sqli-labs第42关使用sleep(5)进行堆叠注入没反应的原因也清楚了。代码中只取了第一条语句的结果,第二条虽然执行了,但是web并没有取它的结果,也就没有等待直接返回了。

posted @ 2021-02-05 15:37  TheTai  阅读(252)  评论(0编辑  收藏  举报