[TOC]
# 文件上传漏洞
在文件上传漏洞中,文件上传本身没什么问题,重点是文件上传到服务器后,服务器怎么解析上传的文件,上传漏洞的成因是开发者没有对用户上传的文件进行严格的过滤,攻击者可以上传危险文件
# 解析漏洞
攻击者利用上传漏洞通常会与 web 容器的解析漏洞相结合,下面简单记录一下常见的 web 容器的解析漏洞
# IIS 解析漏洞
- iis 中当文件夹命名为【*.asa】或者【**.asp】时,其目录下的所有文件都会被当作 asp 文件来解析
- 当文件为 *.asp;1.jpg 时,IIS6.0 同样会将文件当作 asp 解析
# Apache 解析漏洞
Apache 解析文件时有一个原则就是当碰到不认识的扩展名的时候会从后向前解析,知道遇到认识的为止,如果都不认识则会暴漏其源码
其认识的文件扩展名在/conf/mime.types文件下记录
# PHP CGI 解析漏洞
在 php 的配置文件中 cgi.fi:x_pathinfo 在部分版本中默认开启,多数存在于 nginx 与 php 的组合中,当末尾文件不存在时,会向前递归解析
# 简单 waf 绕过
下面两张是 upload-labs 中的图,总结了文件上传常见 waf 的测试方法和绕过思路
想要绕过限制,首先要得知道,开发人员通常通过哪些方法防止文件上传漏洞
- 客户端验证:客户端使用 Javascript 检测,在文件未上传的时候就对文件进行检测
- 服务端验证:在服务端检查文件的 mime 类型,扩展名是否合法,检查文件内容等等
# 下面基于 upload 的绕过总结
# 前端 JS 绕过
# 方法一
浏览器禁用 js 验证
# 方法二
用 burp 抓包修改
将后缀名改成 php
# Content-Type(mime)绕过
所谓 Content-Type 就是互联网媒体类型也就是 MIME 类型,决定浏览器将以什么形式、什么编码读取这个文件
先判断我们用 POST 方式提交 (标签名为 submit) 的变量是否存在,如果存在,则接下来判断 upload 这个文件夹是否存在,如果存在则继续判断我们上传文件的文件类型,如果其文件类型为 image/jpeg 或者为 image/png 或者为 image/gif,那么让 temp_file 这个变量获取存储在服务器中文件的临时名称,然后让 img_path 这个变量为 upload+/+ 被上传文件的名称。再进行判断如果 move_uploaded_file () 函数返回 ture (也就是成功将文件移动到 img_path 路径下) 那么 is_upload 变量为 true,即上传成功
$is_upload = false;
$msg = null;
if (isset($_POST['submit']))//判断我们用POST方式提交(标签名为submit)的变量是否存在
{
if (file_exists(UPLOAD_PATH)) //判断upload这个文件夹是否存在
{
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) //判断我们上传文件的文件类型
{
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']//upload+/+文件名
if (move_uploaded_file($temp_file, $img_path)) //将文件移动到img_path路径下
{
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}
isset (),用于检测变量是否设置,并且不是 NULL。如果该变量存在且非空则返回 TRUE,否则返回 FALSE
file_exists (),检查文件或目录是否存在。如果指定的文件或目录存在则返回 true,否则返回 false。
move_uploaded_file (), 将上传的文件移动到新位置若成功,则返回 true,否则返回 false。
$FILES,FILES 是一个预定义的数组,用来获取通过 POST 方法上传文件的相关信息。如果为单个文件上传,那么 $*FILES 为二维数组;如果为多个文件上传,那么 $*FILES 为三维数组。_
$_FILES'userfile' 客户端文件的原名称。
$_FILES'userfile' 文件的 MIME 类型,需要浏览器提供该信息的支持,例如 “image/gif”。
$_FILES'userfile' 文件被上传后在服务端储存的临时文件名
# 绕过方法
进行抓包,将 Content-Type 修改为允许上传的类型(image/jpeg、image/png、image/gif)三选一。
# 黑名单检测
# 特殊后缀绕过
所谓黑名单就是限制了哪些不可以上传,除去黑名单上的限制都可以。所以我们只要构造黑名单之外的后缀名即可绕过
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array('.asp','.aspx','.php','.jsp');//定义数组
$file_name = trim($_FILES['upload_file']['name']);//移除字符串两侧的空白字符
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');//查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
//php在windows的时候如果文件名+"::$DATA"会把其之后的数据当成文件流处理,不会检测后缀名。 且保持":: $DATA"之前的文件名 1.php::$DATA ==1.php
$file_ext = trim($file_ext); //收尾去空
if(!in_array($file_ext, $deny_ext))//搜索$deny_ext这个数组里的$file_ext,且有为真
{
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
if (move_uploaded_file($temp_file,$img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
strrchr() 函数查找字符串在另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符
array() 函数用于创建数组。
数组类型:
数值数组
带有数字 ID 键的数组
关联数组
带有指定的键的数组,每个键关联一个值
多维数组
包含一个或多个数组的数组
trim() 函数移除字符串两侧的空白字符或其他预定义字符。
相关函数:
ltrim(): 移除字符串左侧的空白字符或其他预定义字符。
rtrim() 移除字符串右侧的空白字符或其他预定义字符。
strtolower(string) 函数把字符串转换为小写。
str_ireplace() 函数替换字符串中的一些字符(不区分大小写)
in_array() 函数搜索数组中是否存在指定的值。如果在数组中找到值则返回 TRUE,否则返回 FALSE
# 绕过方法
只需要构造黑名单以外的后缀名即可进行绕过,所以我们上传后缀名为 .php1、.php2、phtml
等等都可以完成绕过,然后成功上传了一句话木马,用菜刀或者蚁剑连接即可
# 注意
要在 apache 的 httpd.conf 中有如下配置代码:AddType application/x-httpd-php .php .phtml .phps .php5 .pht,如果不配置他是无法解析 php5 代码的
PHPStudy 中 AddType application/x-httpd-php 等 Apache 命令之所以在 Apache 的设置文件中设置后未实现目标效果是由于 PHP 的版本不符导致的,但注意这里的 PHP 版本并不是指 PHP7.3.0、PHP7.4.0 这种版本号,也不是适用于 32 位的 PHP、适用于 64 位的 PHP 这种不同机型的版本,而是 PHP 的 NTS (Non Thread Safe) 与 TS (Thread Safe) 的这种不同版本导致的。
# .htaccess 文件绕过
.htaccess 文件,全称是超文本入口,提供了针对目录改变配置的方法,即在一个特定的文档目录中放置一个包含一个或多个指令的文件,以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。
.htaccess 文件内容
AddType application/x-httpd-php .jpg
这样配置有一个问题,apache 会将所有的文件当作 php 文件解析,这样会明显影响系统的功能,改进代码如下,这样系统就只对文件名包含 “info.png” 字符串的文件进行解析. <FilesMatch "info.png"> setHandler application/x-httpd-php </FilesMatch>
# 注意
.htaccess 文件没有名字,他就是.htaccess 文件,如果你将他改为 4.htaccess 或者其他的什么名字是不可以的,无法解析。在实战中有可能上传上去这个文件会被自动重命名,被重命名了就不可以了。
如果以上操作都弄好了,还是出不来,还是去改 phpstudy 配置文件,其他选项菜单 -- 打开配置文件 ---httpd.conf
箭头指向位置一开始 none, 改为 all 保存,重启 phpstudy,就可以了。
# 大小写绕过
代码中没有对后缀名进行大小写过滤,而且 windows 对大小写不敏感
# 空格绕过
前提:Windows 下 xx.jpg [空格] 或者 xx.jpg. 这两类文件都是不允许存在的,若这样命名,windows 会默认去除空格或点
它没有 trim () 函数,也就是用来去除字符串两端的空格,所以我们如果在上传文件的后缀名里面加上空格,它在黑名单之外,我们就可以成功进行上传
# 点绕过
它没有 deldot () 函数,也就是不会删除我们文件名末尾的点 (.),所以我们可以上传带有小数点的后缀名来进行绕过
# 流文件绕过
我们可以在后缀名后面加上::$DATA 来进行绕过。
# 点空格点绕过
它没有循环验证,也就是说这些收尾去空,删除末尾的点,去除字符串::$DATA,转换为小写这些东西只是验证了一次。所以我们的绕过思路就很简单,在数据包中把后缀名改为.php. .
# 后缀名双写绕过
$file_name = str_ireplace($deny_ext,"", $file_name);
它将 $deny_ext 这个数组里的数据都替换成了空,参考 sql 注入里的双写便可完成绕过。
1.phphpp
1.php
# 图片马绕过
function getReailFileType($filename){
$file = fopen($filename, "rb");//打开一个文件或 URL。"rb" 以二进制方式打开。
$bin = fread($file, 2); //只读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 = "上传出错!";
}
}
}
图片马制作方法: copy a.jpg/b + shell.php/a shell.jpg/b 表示一个二进制文件 + 表示将多个文件合并成一个文件 /a 表示一个 ASCII 文本文件
fopen () 函数打开一个文件或 URL。"rb" 以二进制方式打开。
fread () 函数读取打开的文件。
fclose () 函数关闭一个打开文件
unpack () 函数从二进制字符串对数据进行解包。
intval () 函数用于获取变量的整数值。
# 条件竞争上传
条件竞争漏洞是一种服务器端漏洞,由于服务器端在处理用户请求时,是并发进行的,因此 如果并非处理不当或相关操作逻辑顺序设计的不合理时,将会导致此类问题的发生。
$is_upload = false;
$msg = null;
if(isset($_POST['submit']))
{
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = UPLOAD_PATH . '/' . $file_name;
if(move_uploaded_file($temp_file, $upload_file))
{
if(in_array($file_ext,$ext_arr))
{
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);//将$upload_file名字改为$img_path
$is_upload = true;
}
else
{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传出错!';
}
}
strrpos () 函数查找字符串在另一字符串中最后一次出现的位置(区分大小写)
substr () 函数返回字符串的一部分。
rename () 函数重命名文件或目录。若成功,则该函数返回 true。若失败,则返回 false。
unlink () 函数删除文件。如果成功,该函数返回 TRUE。如果失败,则返回 FALSE。