参考文章:https://www.freebuf.com/articles/web/304130.html
使用靶场:CTFHub
还有附上思维导图:

一、文件上传漏洞的原理与危害
(1)原理
由于未对用户上传的文件进行严格校验(如类型、内容、路径等),攻击者可上传恶意脚本或可执行文件,并通过访问或触发这些文件在服务器上执行任意代码,从而获取系统控制权或破坏服务器。
二、文件上传漏洞主要漏洞
(1)客户端漏洞
客户端漏洞其实就是前端漏洞,也就是文件的过滤是加在前端,这样就造成可以通过修改前端代码实现绕过,而且逻辑也不会太复杂,一般为文件后缀名检查,一般前端验证的特征是错误信息回显很快
例如CTFhub文件上传漏洞中的前端验证,选择php马后点击上传,一瞬间就显示了文件不允许上传

这明显是前端验证,那么我们直接禁用js代码,上传成功

(2)服务端漏洞
服务端的漏洞是最多也是最常用的,以下是常见的漏洞类型:
1)检查文件后缀
a.黑名单类型
黑名单,顾名思义就是不允许上传哪些后缀类型的文件,下面是常见绕过方式
1、上传可特殊可解析文件后缀
一些特殊的文件后缀可以被解析为PHP文件来执行,如下:
.phpX:X代表使用的PHP的大版本号,用于PHP相应版本的脚本文件
.phtml:嵌入HTML的PHP文件
.inc:PHP包含文件,通常用于包含其他PHP脚本的代码片段
但是后两个可能需要修改配置文件才能被当做php来执行
2、使用.htaccess文件
.htaccess文件是一个用于配置Apache Web服务器的配置文件,通常放置在网站目录中。它允许在不修改主服务器配置文件(如httpd.conf)的情况下,对特定目录进行配置和定制。
那么这里是.htaccess文件的一些作用:
URL重写(Rewrite Rules)
通过mod_rewrite模块,可以实现URL重写、重定向和友好的URL(如将example.com/page.php?id=1重写为example.com/page/1)。
访问控制
限制特定IP地址或用户访问目录或文件。
设置密码保护目录(Basic Authentication)。
MIME类型和文件处理
定义文件扩展名的MIME类型(如将.inc文件解析为PHP)。
配置文件的处理方式(如将.jpg文件当作PHP文件执行)。
错误页面定制
自定义错误页面(如404、500等),提升用户体验。
缓存控制
设置浏览器缓存策略,优化网站性能。
字符编码设置
指定文件的字符编码(如AddDefaultCharset UTF-8)。
禁止目录列表
防止目录在没有索引文件(如index.html)时被列出。
其他作用暂时不谈,我们文件上传漏洞主要是使用它的MIME类型和文件处理的功能,通过上传.htaccess文件将其他允许上传的文件类型解析为PHP来执行
3、uer.ini文件绕过
参考文章
https://blog.csdn.net/cosmoslin/article/details/120793126
当连.htaccess 也被过滤时,我们就可以尝试使用 usr.ini 文件来实现绕过。user.ini可以用与任何一个使用fastagi模式的中间件环境。但是依赖于目录下已存在的可执行文件,因为绕过原理实际上是文件包含。
auto_prepend_file = <filename> //包含在文件头
auto_append_file = <filename> //包含在文件尾
在当前目录的usr.ini文件中写入这项配置即可自动在其他文件中包含指定文件内容
4、后缀大小写绕过
这个就不多说,就是字面意思,而且一般情况不会有这个漏洞
5、点、空格绕过
在文件末尾加上点加空格加点,原理是windows等系统默认删除文件后缀的.和空格,删除之后就变成可执行文件了,linux并不会
6、::$DATA绕过
此方法只适用于Windows系统,在Windows的NTFS文件系统中,一个文件真正的文件名称格式:
<文件名>:<流名>:<流种类>
参考文章:
https://blog.csdn.net/qq_53377571/article/details/125877899
关于ADS数据流的扩展文章
https://blog.csdn.net/AmrYu/article/details/122770431
那么也就是说,文件存储时,如果没有备用数据流,本身就是以filename::$DATA
的样式来存储的,但是 ::$DATA并不会显示,那么就可以实现绕过
6、双写绕过
对于定义了危险词语替换的情况,就可以使用双写绕过。例如危险词是php,会将php替换为空,那么我们把后缀改为.pphphp,首先匹配到第一个php,替换为空后再次组成php,绕过成功
b.白名单类型
1、MIME绕过
MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准 MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据 在HTTP 协议中,使用Content-Type 字段表示文件的MIME 类型。
通过抓包修改MIME类型

2、%00截断
在URL中%00表示ASCII码中的0,而0作为特殊字符保留,表示字符结束,当url中出现%00时就会认为读取已结束,而忽略后面上传的文件或图片,只上传截断前的文件或图片
要求:php版本小于5.3.4,php的magic_quotes_gpc为OFF状态
文件上传路径可控
该种绕过方式可以绕过前端和后端(要求比较多)针对后缀名进行的过滤
例如:我们上传 1.php%00.jpg 时,首先后缀名是合法的jpg格式,可以绕过前端的检测。上传到后端后,后端判断文件名后缀的函数会认为其是一个.jpg格式的文件,可以躲过白名单检测。但是在保存文件时,保存文件时处理文件名的函数在遇到%00字符认为这是终止符,于是丢弃后面的 .jpg,于是我们上传的 1.php%00.jpg 文件最终会被写入 1.php 文件中并存储在服务端。
GET绕过
GET传参时,%00字符会被自动解码,所以直接使用即可
POST绕过
POST传参时,%00不会被自动解码,所以我们需要提前进行解码

至于0x00和0x0a和%00是一样的,只是他们是16进制,需要直接在bp的HEX值中修改
c.中间件解析漏洞
我觉得中间件解析不应该属于黑名单和白名单,因为都可以用,实际上绕过原理类似于.htaccess文件绕过的原理,中间件一般是指Apache,Nginx,IIS等Web服务器。这个漏洞其实还是得靠爆出的已知漏洞,我觉得我应该是没有能力发现的😂
2)检查文件内容
a. 文件头检查
一般情况下,针对文件头的绕过都可以通过在文件开头中加入 GIF89a 标识,并将图片更改为图片格式即可绕过
绕过自构建的针对文件头的WAF,例如
function getReailFileType($filename) {
// 打开文件,以二进制只读模式
$file = fopen($filename, "rb");
// 读取文件的前2个字节(文件头)
$bin = fread($file, 2);
// 关闭文件
fclose($file);
// 解包二进制数据,获取文件头的两个字节
$strInfo = @unpack("C2chars", $bin);
// 将两个字节拼接成整数,用于判断文件类型
$typeCode = intval($strInfo['chars1'] . $strInfo['chars2']);
// 根据文件头判断文件类型
$fileType = '';
switch ($typeCode) {
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType; // 返回文件类型
}
// 初始化变量
$is_upload = false;
$msg = null;
// 检查是否提交了文件
if (isset($_POST['submit'])) {
$temp_file = $_FILES['upload_file']['tmp_name']; // 获取临时文件路径
$file_type = getReailFileType($temp_file); // 获取文件真实类型
if ($file_type == 'unknown') {
$msg = "文件未知,上传失败!"; // 文件类型不合法,提示失败
} else {
// 生成新的文件名,包含随机数和时间戳
$img_path = UPLOAD_PATH . "/" . rand(10, 99) . date("YmdHis") . "." . $file_type;
// 将临时文件移动到指定目录
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true; // 上传成功
} else {
$msg = "上传出错!"; // 上传失败
}
}
}
或者是使用了 getimagesize 函数的情况
getimagesize() 函数用于获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息。
语法格式:
array getimagesize ( string $filename [, array &$imageinfo ] )
getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型及图片高度与宽度。
例如
function isImage($filename) {
// 允许的图片文件后缀
$allowed_types = '.jpeg|.png|.gif';
// 检查文件是否存在
if (file_exists($filename)) {
// 获取文件信息(包括文件类型)
$info = getimagesize($filename);
// 将文件类型转换为文件后缀
$ext = image_type_to_extension($info[2]);
// 检查文件后缀是否在允许的列表中
if (stripos($allowed_types, $ext) !== false) {
return $ext; // 返回文件后缀
} else {
return false; // 文件类型不合法
}
} else {
return false; // 文件不存在
}
}
// 初始化变量
$is_upload = false;
$msg = null;
// 检查是否提交了文件
if (isset($_POST['submit'])) {
$temp_file = $_FILES['upload_file']['tmp_name']; // 获取临时文件路径
$res = isImage($temp_file); // 验证文件是否为合法图片
if (!$res) {
$msg = "文件未知,上传失败!"; // 文件类型不合法,提示失败
} else {
// 生成新的文件名,包含随机数和时间戳
$img_path = UPLOAD_PATH . "/" . rand(10, 99) . date("YmdHis") . $res;
// 将临时文件移动到指定目录
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true; // 上传成功
} else {
$msg = "上传出错!"; // 上传失败
}
}
}
抑或是使用了 PHPExif 判断图片类型的情况:
PHPExif 是一个开源的PHP库,旨在为用户提供便捷的图像EXIF元数据访问功能。EXIF(Exchangeable Image File Format)是嵌入在图像文件中的元数据,包含了拍摄时间、相机型号、曝光参数等重要信息。PHPExif通过封装原生PHP功能或外部工具(如Exiftool),提供了一个统一的API接口,使得开发者能够轻松地读取和处理这些元数据。
例如
function isImage($filename) {
// 使用 exif_imagetype 函数判断图像类型
$image_type = @exif_imagetype($filename); // 使用 @ 抑制错误输出
// 根据图像类型返回对应的文件后缀
switch ($image_type) {
case IMAGETYPE_GIF:
return "gif";
break;
case IMAGETYPE_JPEG:
return "jpg";
break;
case IMAGETYPE_PNG:
return "png";
break;
case IMAGETYPE_WEBP:
return "webp";
break;
default:
return false; // 文件不是合法的图片类型
break;
}
}
// 初始化变量
$is_upload = false;
$msg = null;
// 检查是否提交了文件
if (isset($_POST['submit'])) {
$temp_file = $_FILES['upload_file']['tmp_name']; // 获取临时文件路径
$res = isImage($temp_file); // 验证文件是否为合法图片
if (!$res) {
$msg = "文件未知,上传失败!"; // 文件类型不合法,提示失败
} else {
// 生成新的文件名,包含随机数和时间戳
$img_path = UPLOAD_PATH . "/" . uniqid() . "_" . rand(1000, 9999) . "." . $res;
// 将临时文件移动到指定目录
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true; // 上传成功
} else {
$msg = "上传出错!"; // 上传失败
}
}
}
b.二次渲染绕过
原理:
在我们上传文件后,网站会对图片进行二次处理(格式、尺寸要求等),服务器会把里面的内容进行替换更新,处理完成后,根据我们原有的图片生成一个新的图片并放到网站对应的标签进行显示。
绕过:
1、配合文件包含漏洞:
通过对比处理前后的图片,确认不会被处理的数据部分,通过在这部分数据中写入木马绕过处理,再配合文件包含漏洞执行木马获取webshell
2、可以配合条件竞争:
这里二次渲染的逻辑存在漏洞,先将文件上传,之后再判断,符合就保存,不符合删除,可利用条件竞争来进行爆破上传