前一段时间的嘶吼CTF做的我自闭了,正好现在wp出来了,学长也搞了环境,顺手白嫖一下,试着按照wp做了一遍,学习了一下新知识。
首先,题目提示我们这是文件上传,源码也告诉了我们,但我试了一下upload.php等发现没有文件上传的页面,估计是要自己写脚本上传文件。再看看源码:
<?php
namespace Home\Controller;
useThink\Controller;
classIndexControllerextendsController
{
publicfunctionindex()
{
show_source(__FILE__);
}
publicfunctionupload()
{
$uploadFile=$_FILES['file'] ;
if (strstr(strtolower($uploadFile['name']), ".php") ) {
return false;
}
$upload=new\Think\Upload();// 实例化上传类
$upload->maxSize =4096 ;// 设置附件上传大小
$upload->allowExts =array('jpg', 'gif', 'png', 'jpeg');// 设置附件上传类型
$upload->rootPath='./Public/Uploads/';// 设置附件上传目录
$upload->savePath='';// 设置附件上传子目录
$info=$upload->upload() ;
if(!$info) {// 上传错误提示错误信息
$this->error($upload->getError());
return;
}else{// 上传成功获取上传文件信息
$url=__ROOT__.substr($upload->rootPath,1).$info['file']['savepath'].$info['file']['savename'] ;
echo json_encode(array("url"=>$url,"success"=>1));
}
}
}
可以看到,文件名会先进行大小写转换然后添加.php后缀,也就是说大小写绕过是不可能的了,然后限制了文件大小和文件类型(jpg,gif,png,jpeg),也就是说只要上传php文件,把后缀改成.jpg,代码就会自己将.jpg后缀换成.php(实际上因为$upload->allowExts
用法错误,所以这个限制是没用的)。上传失败返回失败信息,成功后返回一个字符串,该字符串是数组经过json编码的(结果和序列化长的很像,有时间看看区别在哪)。我们先试着上传一个文件试试。
这里,我参照大佬的wp使用的是requests库的文件上传接口。
在参考了大佬关于requests库的部分描述
之后,我上传了正常的txt文件(这里我是使用的thinkphp的默认文件上传路径),它显示:
我访问了该目录之后发现能够看到自己上传的文本内容,但文件名字变了。
我又试图上传一个PHP文件,发现没有回显,毫无疑问是失败了。
看来大佬的wp之后才知道原来是要用条件竞争绕过strstr的后缀检验,然后我依照大佬的wp写py脚本上传文件,然后爆破出php文件名
#文件上传脚本
import requests
url = 'http://7a4fb2e4-7ba4-49b0-a137-450ceea340d1.node3.buuoj.cn/index.php/home/index/upload'
file1 = {'file':open('special_dict.txt','r')}
file2 = {'file[]':open('Ckinfe.php','r')}//upload()不传参时即是批量上传所以用[]
r = requests.post(url,files = file1)
print r.text
r = requests.post(url,files = file2)
print r.text
r = requests.post(url, files = file1)
print r.text
现在只要最后一步爆破文件名就可以做出来了。很可惜的是我的脚本跑了很久也没跑出来。
该题的大致流程听起来很简单:条件竞争上传文件、爆破文件名,但都建立在对于thinkphp有一定了解的程度上,如:限制文件上传类型,乍一看是对的,其实是错误用法,所以文件类型限制无效。还有在上传文件时修改的文件名,通过查看源码发现它是以uniqid函数来重命名的而该函数是基于微秒的当前时间来更改文件名的,所以才能有爆破文件名的机会。
没什么好说的,我还是太菜了QAQ。