代码审计-thinkphp中的文件上传漏洞

前言

这两天审计了一个基本组成是thinkphp的代码,里面有一个文件上传的漏洞,下面记录一下审计的整个流程

在fofa上面搜索一下:"web/static/css/chunk-elementUI.f92cd1c5.css",搜索到的链接其前后端便是这次审计的代码框架

审计

在开始审计该框架的时候,我便已经知道这个框架有一个文件上传的漏洞,所以审计的过程中我便只看了跟文件上传有关的代码

该框架是一个多应用模式,app目录下面有admin,api,super,web四个目录,身为普通用户的我们是在web目录下的,也只能够操作web目录下面的控制器方法

web目录下面有一个Upload.php文件,具体代码如下:

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
<?php

namespace app\web\controller;
use think\facade\Filesystem;

class Upload extends Base
{
/**
*上传图片
*/
public function image()
{
try {
// 获取上传的文件(名称为 'image')
$file = request()->file('image');
// 将文件保存到 'public' 磁盘的 'image' 目录中,文件名使用唯一ID
$path = Filesystem::disk('public')->putFile('image', $file, 'uniqid');
// 获取文件扩展名,最后一个.处
$ext = strrchr($path, '.');
// 检查文件扩展名是否在允许的格式中(.jpg, .jpeg, .png, .gif)
if (!in_array($ext, ['.jpg', '.jpeg', '.png', '.gif'])) {
// 如果文件格式不正确,删除文件
@unlink('./upload/' . $path);
// 返回错误信息,指明只能上传指定格式的图片
return errorJson('只能上传jpg/png/gif格式的图片');
}
// 将文件保存到OSS(对象存储服务),并返回文件的URL
$url = saveToOss('./upload/' . $path);
// 返回成功信息,包括文件的URL
return successJson([
'path' => $url
]);
} catch (\Exception $e) {
// 捕获异常并返回错误信息
return errorJson($e->getMessage());
}
}
}

这是上传头像的代码

先开启proxy插件,然后更改一下头像,在bp代理模块的HTTP历史记录中找到/web.php/upload/image条目,发送到重放器中

上面代码要求的文件名后缀只能够是.jpg, .jpeg, .png, .gif,但是是通过$ext = strrchr($path, '.');来获取后缀名进行检测的,而这个方法只是获取最后一个.处的内容,于是我想尝试一波能不能通过00截断进行绕过

image-20240807161654568

成功上传,但是看来一眼回显的链接文件名后缀是jpg形式的,两眼一黑

后面发现这个框架是thinkphp6的框架,已经超出了00截断嫩影响的版本了

于是尝试用其他的方法,都失败了

这个时候豆哥点拨了一句:web目录下面有好几处文件上传的地方,看看thinkphp框架自己包装了哪些文件上传的方法

于是问了一下gpt,全局搜索了request()->file方法,在web目录下面有好几个文件都用了这个方法

一个个文件审计过去,最终确定了文件上传的漏洞点位于Video.php文件下的uploadMedia()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public function uploadMedia()
{
try {
$file = request()->file('file');
$mine = $file->getMime();
if (!in_array($mine, ['image/png', 'image/jpeg', 'video/mp4'])) {
return errorJson('仅支持上传jpg/png/mp4格式文件');
}
if ($mine == 'video/mp4') {
$fileType = 'video';
} else {
$fileType = 'image';
}
$path = Filesystem::disk('public')->putFile($fileType, $file, 'uniqid');
$url = saveToOss('./upload/' . $path);
return successJson([
'type' => $fileType,
'path' => $url
]);
} catch (\Exception $e) {
return errorJson($e->getMessage());
}
}

那么为什么是这里呢,我们看看该方法的代码

它是通过检测文件的Mine值来判断文件是否符合要求的,而Mine值可以通过bp抓包修改content-type的值来更改

该方法不检查文件的后缀名,所以我们就可以上传php文件了

话不多说,开始操作

我们先构造一个url来访问该方法:/web.php/video/uploadMedia

要注意是web.php而不是web

访问后报错如下:

image-20240807163422300

问题不大,成功访问到了uploadMedia()方法,该报错只是getMine()的值为空而已

接下来我们需要做的就是构造一个文件上传的请求包,要注意的是该方法的参数名是file(鄙人狠狠地踩进了这个坑中)

所以构造的请求包如下所示:

image-20240807163933979

一切都很完美了呀,可是为什么还是500错误呢

报错 {"code":0,"message":"Call to undefined function think\\finfo_open()"} 表明在你的 PHP 环境中缺少 finfo_open 函数。这个函数是 PHP 的 Fileinfo 扩展的一部分,该扩展用于检测文件的 MIME 类型

原来是系统没有启用该扩展,所以导致文件上传不上去

但是客观上面还是存在该漏洞的,只是我们不能利用罢了(呜呜呜)

结语

这次代码审计比第一次要轻松,主要还是因为提前学习了thinkphp框架,所以对于系统的结构,要从哪里下手看代码进行审计还是比较清楚的