目录导航
声明
本文仅作个人学习记录,涉及技术仅供学习参考,禁止用于其他!!!未经授权请勿利用文章中提及的技术对任何计算机系统进行非法攻击。利用此文所提供的技术而造成的直接或间接后果和损失,均由使用者本人负责。
源码简略分析
由于我是直接拿到的别人给的源码,源码链接:https://www.yzzpan.com/#sharefile=gnxKb5Ah_109925
查看源码,很容易发现框架为thinkphp,从更新日志中发现版本为5.0.24。
thinkphp文件目录的功能:Application 里面是控制器和模块;Extend 扩展的类库;Public 入口文件;Runtime 运行时的缓存文件;thinkphp 核心框架。很明显题目给出的下载源码是不全的,先不管它,大致翻了下文件内容,发现了application\index\controller\index.php文件存在处理上传的方法,当上传图片时,进入upload_as_image方法进行过滤,上传文本时进入upload_as_text方法进行过滤。到这里猜测这道题应该是考文件上传绕过了,具体代码在下方。
环境搭建
做这类题还是搭建一个环境进行实操和代码分析结合更快捷,GitHub上找到了thinkphp5.0.24的环境GitHub – rama291041610/Thinkphp5.0.24-Unserialize-Vulnerability,git下载。
进入到下载的目录,用docker-compose up -d启动容器
docker ps查看正在运行的容器,记录一下container id为:141aa8342fac
进入容器,查看容器内部当前所在目录
命令:docker cp /home/xu/Desktop/tp524.zip 141aa8342fac:/var/www/html将我们的源码打包后复制到容器内部,并用unzip -o解压并覆盖相同文件,这样就解决了题目给出源码不全的问题了。
容器原本的首页内容和替换后的内容如下:
源码详细分析
先说一个thinkphp的细节:我们查看源码发现上传表单设置:
<form method="post" enctype="multipart/form-data" action=/index/index/upload.html>
但是访问http://ip:port/index/index/upload.html是不成功的,因为thinkphp的访问格式是:/控制器名/模块名/方法名。具体有两种访问方式,一是传参如http:// ip:port/?s=/控制器名/模块名/方法名,二是伪静态如http:// ip:port/控制器名/模块名/方法名。
具体处理上传内容的index模块代码如下:分析代码可以发现,只能上传图片和文本类型,当上传图片类型时,进入upload_as_image方法,该方法会判断上传图片后缀是不是jpg、png、gif的其中一种,如果都不是,那就强制修改后缀为jpg,然后将文件保存在当前../uploads/files/当前时间年月日时分秒/当前时间年月日时分秒+1000至9999的随机数。
如果上传文本类型,先检查过滤后缀,没有列表[‘php’, ‘html’, ‘js’, ‘css’, ‘sql’, ‘phtml’, ‘shtml’, ‘php5’, ‘php7’, ‘phtm’, ‘pht’, ‘php8’, ‘php4’, ‘.htaccess’, ‘tpl’]中的后缀,那么保存文件方式与图片类型一致。
当上传时带上get请求中带上hw_file_name参数时,两种类型上传的文件名都不会被保存为随机文件名。
仔细观察上面的过滤后缀的列表,感觉此处有点坑,我一看过去以为.htaccess文件不能上传,但列表中的htaccess前面多了一个点,上传 .htaccess文件经过$file_ext = explode(‘.’,$file_name);这样处理后,进入列表判断时是htaccess,刚好不会被拦截。还是不够熟练吧😊。
那么此题的思路就是先上传 .htaccess 文件,而上传image类型时,存在目录穿越,可以将上传的jpg图片保存到.htaccess目录,那么htaccess文件需要设置为将图片解析为php。还要一个思路是上传htaccess同时上传文本,保证保存在同一个目录,本文利用第一种方法。
<?php
namespace app\index\controller;
class Index
{
public function index()
{
return '<form method="post" enctype="multipart/form-data" action='.url('index/index/upload').'>
<input type="file" name="hw_file">
<input type="submit" value="上传">';
}
public function upload()
{
if (request()->isPost()){
$file = $_FILES['hw_file']??'';
if(!$file){
return json(['code'=>0,'msg'=>'请选择文件']);
}
$file_name = $file['name'];
$file_tmp = $file['tmp_name'];
$file_size = $file['size'];
if ($file_size > 1024*1024*2){
return json(['code'=>0,'msg'=>'文件大小不能超过2M']);
}
$file_error = $file['error'];
if ($file_error > 0){
return json(['code'=>0,'msg'=>'上传失败']);
}
$file_type = $file['type'];
$file_ext = explode('.',$file_name);
$file_ext = strtolower(end($file_ext));
if(strstr($file_type, "image/")){
if($this->upload_as_image($file_ext, $file_tmp, "../uploads/images/".date('YmdHis')."/", ["gif"], request()->get("hw_file_name")??FALSE)){
return json(['code'=>1,'msg'=>'上传成功']);
} else {
return json(['code'=>0,'msg'=>'上传失败']);
}
} else {
if($this->upload_as_text($file_ext, $file_tmp, "../uploads/files/".date('YmdHis')."/", request()->get("hw_file_name")??FALSE)){
return json(['code'=>1,'msg'=>'上传成功']);
} else {
return json(['code'=>0,'msg'=>'上传失败']);
}
}
} else {
return json(['code'=>0,'msg'=>'请求方式错误']);
}
}
public function upload_as_image($image_type, $image_tmp_file, $upload_base_dir, $file_ext_black_list, $image_filename=FALSE)
{
if(in_array($image_type, $file_ext_black_list)){
return 0;
}
switch ($image_type) {
case 'jpg':
$image_ext = '.jpg';
break;
case 'png':
$image_ext = '.png';
break;
case 'gif':
$image_ext = '.gif';
break;
default:
$image_ext = '.jpg';
break;
}
$image_size = getimagesize($image_tmp_file);
$image_width = $image_size[0];
$image_height = $image_size[1];
if($image_width > 200 || $image_height > 200){
return 0;
}
if ($image_filename === FALSE) {
$image_filename = date('YmdHis') . rand(1000, 9999) . $image_ext;
} else {
$image_filename = $image_filename . $image_ext;
}
if(!file_exists($upload_base_dir)){
mkdir($upload_base_dir, 0777, true);
}
$image_file_path = $upload_base_dir . $image_filename;
rename($image_tmp_file, $image_file_path);
return 1;
}
public function upload_as_text($text_type, $text_tmp_file, $upload_base_dir, $text_filename=FALSE)
{
if(strstr($text_type, "ph") || in_array($text_type, ['php', 'html', 'js', 'css', 'sql', 'phtml', 'shtml', 'php5', 'php7', 'phtm', 'pht', 'php8', 'php4', '.htaccess', 'tpl'])){
return 0;
}
if (strstr($text_filename, ".") || strstr($text_filename, "/")) {
return 0;
}
if( strlen($text_type) == 0){
$text_ext = "";
} else {
if(!ctype_alpha($text_type)){
return 0;
}
$text_ext = "." . $text_type;
}
if ($text_filename === FALSE) {
$text_filename = date('YmdHis') . rand(1000, 9999) . $text_ext;
} else {
$text_filename = $text_filename . $text_ext;
}
if(strlen($text_filename) == 0 || strstr($text_filename, "/") ||!preg_match('/[A-Za-z0-9_]/is', $text_filename)){
return 0;
}
if(!file_exists($upload_base_dir)){
mkdir($upload_base_dir, 0777, true);
}
$text_file_path = $upload_base_dir . "/" . $text_filename;
rename($text_tmp_file, $text_file_path);
return 1;
}
}
首先上传.htaccess文件,设置解析jpg为php,记住上传时间,GMT代表格林威治时间(北京市位于东8区,计北京时间要加8小时),所以上传时间为20221128003803,那么htaccess保存的路径为uploads/files/20221128003803/.htaccess。
在上传图片一句话马,并目录穿越保存到与htaccess文件相同目录。
最后很抱歉,这个环境毕竟不是官方环境,来做这个题有一些问题,中途我换了下虚拟机,也搜了下thinkphp和apache静态资源目录的设置,最终还是访问不到test.jpg文件👎,附一张别人成功的图吧。
转载请注明出处及链接