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
的环境,在终端中执行就行了。安装php
和php-mysql
的命令如下。
sudo apt install php7.2-cli
sudo apt install php-mysql
切换到包含test1.php的目录下,脚本执行方式为php test1.php
源代码分析
- 上面
test2.php
中的代码,虽然有多条sql查询,但是只取了一次结果。而test1.php
则是通过循环依次取多条sql语句的结果。 - 上面两份代码中都分别包含3条查询。第一条是正常的查询,第二和第三条分别是睡眠20和30秒
执行结果
先执行php test1.php
,结果如下:
可以看到,依次输出了对应的结果,查询数据库的历史记录,也可以看到都依次成功执行了,没啥问题。
然后执行php test2.php
,结果如下:
然后再查询数据库的执行记录,如下图所示
虽然这个程序只取了第一条语句的结果,但是所有三条查询均执行了。而且,在程序刚执行第二个查询,也就是select sleep(20)
的时候,迅速查看数据库的历史记录如下
可以看到第三条查询sleep(30)
还未执行。现在基本可以得出结论了。
结论
mysqli_multi_query
中的多条sql查询(由分号'
分隔),无论是否取结果,取几个结果,都将提交给数据库执行,并且是顺序执行,执行完一条再执行下一条。
这个sqli-labs第42关使用sleep(5)
进行堆叠注入没反应的原因也清楚了。代码中只取了第一条语句的结果,第二条虽然执行了,但是web并没有取它的结果,也就没有等待直接返回了。