文件上传漏洞总结

image-20210915202850617

一、不安全的文件上传漏洞概述

在文件上传的功能处,若服务端脚本语言未对上传的文件进行严格验证和过滤,导致恶意用户上传恶意的脚本文件时,就有可能获取执行服务端命令的能力,这就是文件上传漏洞。文件上传漏洞对Web应用来说是一种非常严重的漏洞。

一般情况下,Web应用都会允许用户上传一些文件,如头像、附件等信息,如果Web应该没有对用户上传的文件进行有效的检查过滤,那么恶意用户就会上传一句话木马等webshell,从而达到控制web网站的目的。

二、漏洞原理

由于程序员在对用户文件上传部分的控制不足或者处理缺陷,而导致用户可以越过其本身权限向服务器上传可执行的动态脚本文件,并通过此脚本文件获得了执行服务端命令的能力。

高危出发点:

相册、头像上传视频、照片分享附件上传(论坛发帖、邮箱)文件管理器

三、漏洞危害

1、代码执行

上传文件是web脚本语言,服务器的web容器解释并执行了用户上传的脚本,导致代码执行,甚至导致网站甚至整个服务器被控制

2、域控制

上传文件是flash的策略文件crossdomiain.xml,黑客用以控制flash在该域下的行为;

3、网站挂马

上传文件是病毒、木马,黑客用以诱骗用户或者管理员下载执行;

4、钓鱼

上传文件是钓鱼图片或为包含了脚本的图片,在某些版本的浏览器中会被作为脚本执行,被用于钓鱼和欺诈。

三、上传漏洞满足条件

首先,上传的文件能够被web容器解释执行。所以文件上传后所在的目录要是web容器所覆盖到的路径。

其次,用户能够从web访问这个文件。如果文件上传了,但用户无法通过web访问,或者无法得到web容器解释这个脚本,那么也不能称之为漏洞。 最后,用户上传的文件若被安全检查、格式化、图片压缩等功能改变了内容,则也可能导致攻击不成功。

四、防御

将上传文件与web服务隔离 白名单过滤、限制上传文件类型 文件上传路径设置为不可执行权限 检查文件上传路径 自带函数检测 自定义函数检测 图片渲染 对上传文件重命名 对文件内容压缩,重新生成文件内容 检查文件内容

五、上传漏洞绕过

详情看图片,后面有一些场景的题目配合学习

upload-labs通关笔记

img

Pass-1 使用js对不合法图片进行检查

需要上传个webshell上去 但是限制了上传的文件类型

image-20210705114326369

F12查看javascrip内容

image-20210705115023630

代码审计

function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";    //前端白名单,只允许这三个格式
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name) == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);     //输出errMsg
return false;
}
}

方法一 :本地删除js代码上传

F12点击查看器,将其中的js过滤代码(上面那段)删除掉,ctrl+s保存在本地桌面上,在桌面上右键文本方式打开,寻找action(目标),ctrl+f,没有找到,说明没有上传目标,手动添加,重新刷新网页正常上传正常图片,查看网络,可以看到一个post数据包,200ok,里面有一个地址

action="http://127.0.0.1/upload/Pass-01/index.php"

将这个代码添加在<from>标签

<div id="upload_panel">
<ol>
<li>
<h3>任务</h3>
<p>上传一个<code>webshell</code>到服务器。</p>
</li>
<li>
<h3>上传区</h3>
<form action="http://127.0.0.1/upload/Pass-01/index.php" enctype="multipart/form-data" method="post" onsubmit="return checkFile()">
<p>请选择要上传的图片:</p><p>
<input class="input_file" type="file" name="upload_file">
<input class="button" type="submit" name="submit" value="上传">
</p></form>
<div id="msg">
</div>
<div id="img">
</div>
</li>
</ol>
</div>

重新上传一个a.php文件,(<?php phpinfo(); ?>)

在网站根目录result可以查看到a.php文件,说明上传成功。

或者访问:http://127.0.0.1/upload/upload/a.php

image-20210705162735149

方法二:bp抓改

浏览器172.21.172.56访问靶场,修改php文件后缀改为png文件,上传,bp抓包,修改数据包后缀,又改回php,发送数据包,再次访问http://172.21.172.56/upload/upload/b.php出现phpinfo信息说明成功

方法三:火狐禁用js过滤

接着再地址栏输入about:config,点击回车键。

image-20210705163655136

接着看到如下画面。去掉下次任显示此警告前面的方框里的钩,再点击我 保证会小心按钮

点击后看到如下画面,在搜索地址栏中输入javascript.enabled

image-20210705163810072

改为flase

image-20210705163847132

直接上传c.php就可以了

访问:http://172.21.172.56/upload/upload/c.php

出现信息,成功

方法四:修改js:前端过滤不安全

js代码很明显是白名单,只允许三种通过,那么我们可以手动添加一个规则

image-20210705164535066

直接上传php文件

Pass-2 在服务端对数据包的MIME进行检查

右键网页源码,没有发现js过滤

网站根目录查看index.php源码

<?php
include '../config.php';
include '../head.php';
include '../menu.php';
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {    //isset — 检测变量是否已设置并且非 null
if (file_exists(UPLOAD_PATH)) {    // file_exists  检查文件或目录是否存在 ,UPLOAD_PATH如果存在
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {  //如果文件类型是image/jpeg  image/png  image/gif(白名单)  
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];          
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';  //非image/jpeg  image/png  image/gif,重新上传
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}
?>

不是在js中,而是在服务器端进行了限制

MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准 MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据 在HTTP 协议中,使用Content-Type 字段表示文件的MIME 类型。

所以抓包修改Content-Type

image-20210705173434293

image-20210705173635908

200ok

仅仅判断content-type类型,因此上传a.php抓包修改content-type为图片类型:image/jpeg、image/png、image/gif 就可了

Pass-3 特殊后缀绕过黑名单

源码分析

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {   //检查文件或目录是否存在
$deny_ext = array('.asp','.aspx','.php','.jsp');  //数组deny_exit相当于黑名单
$file_name = trim($_FILES['upload_file']['name']);   //trim()去除字符串首尾处的空白字符(或者其他字符)
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');   // 查找指定字符在字符串中的最后一次出现 
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //收尾去空
if(!in_array($file_ext, $deny_ext)) {   //检查数组中是否存在某个值,如果不存在 执行下一步
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;   //date()重置时间,时间戳   //rand产生一个随机整数       
if (move_uploaded_file($temp_file,$img_path)) {  //将上传的文件移动到新位置
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
/*
move_uploaded_file(string `$filename`, string `$destination`)
filename
上传的文件的文件名。
destination
移动文件到这个位置。
*/

是个黑名单,不允许上传.asp,.aspx,.php,.jsp后缀的文件

那最简单的绕过办法,用特殊后缀名

.phtml

.phps

.php5

.pht

类似的 前提是apache的配置文件httpd.conf中有如下配置

AddType application/x-httpd-php .php .phtml .phps .php5 .pht

实质就是添加可以执行php的文件类型

image-20210706144817624

回复告诉我们路径以及文件名了。图片上传后右键也能得到图片地址

Pass-4 .htaccess绕过黑名单

源码分析

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".ini");
$file_name = trim($_FILES['upload_file']['name']);//trim()去除字符串首尾处的空白字符(或者其他字符)
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.'); 
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //收尾去空

if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

黑名单拒绝了几乎所有有问题的后缀名,除了.htaccess 前提条件(1.mod_rewrite模块开启。2.AllowOverride All

因此先上传一个.htaccess文件,内容如下:SetHandler application/x-httpd-php

这样所有文件都会当成php来解析

在上传一个 1.jpg,(内容写成 <?php phpinfo();?>)之后访问就会以php来执行

Pass-5 .user.ini绕过黑名单

源码分析

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$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
$file_ext = trim($file_ext); //首尾去空

if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

更完善的黑名单.htaccess文件也被禁了

但是.ini没禁 所以可以上传.user.ini文件 内容是 auto_prepend_file=1.jpg

让所有php文件都“自动”包含1.jpg文件

试了半天根本不行,介绍另一个方法

点+空格+点绕过image-20210706160431394

image-20210706160455855

Pass-6-大小写绕过

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空

if (!in_array($file_ext, $deny_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 = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

更完善的黑名单

但是大小写的函数去掉了

所以可以后缀名大写绕过

$file_ext = strtolower($file_ext); //转换为小写

这里没得

image-20210706161303404

抓包看看,文件改成啥名字了

image-20210706161359152

image-20210706161500246

Pass-7 空格绕过黑名单

源码分析

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");
$file_name = $_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

if (!in_array($file_ext, $deny_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 = '此文件不允许上传';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

对比前面的题 这题没有对后缀名进行去空 所以在后缀名里加个空格就能绕过

$file_ext = trim($file_ext); //收尾去空    $file_name = trim($_FILES['upload_file']['name']);   //trim()去除字符串首尾处的空白字符(或者其他字符)

这里没有

image-20210706162153233

Pass-8 点绕过黑名单

源码分析

$is_upload = false;$msg = null;if (isset($_POST['submit'])) {    if (file_exists(UPLOAD_PATH)) {        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");        $file_name = trim($_FILES['upload_file']['name']);        $file_ext = strrchr($file_name, '.');        $file_ext = strtolower($file_ext); //转换为小写        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA        $file_ext = trim($file_ext); //首尾去空                if (!in_array($file_ext, $deny_ext)) {            $temp_file = $_FILES['upload_file']['tmp_name'];            $img_path = UPLOAD_PATH.'/'.$file_name;            if (move_uploaded_file($temp_file, $img_path)) {                $is_upload = true;            } else {                $msg = '上传出错!';            }        } else {            $msg = '此文件类型不允许上传!';        }    } else {        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';    }}

对比前面 没有对后缀名末尾的点进行处理

$file_name = deldot($file_name);//删除文件名末尾的点

没有这个

image-20210706162736168

Pass-9::$DATA绕过黑名单

源码分析

$is_upload = false;$msg = null;if (isset($_POST['submit'])) {    if (file_exists(UPLOAD_PATH)) {        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");        $file_name = trim($_FILES['upload_file']['name']);        $file_name = deldot($file_name);//删除文件名末尾的点        $file_ext = strrchr($file_name, '.');        $file_ext = strtolower($file_ext); //转换为小写        $file_ext = trim($file_ext); //首尾去空                if (!in_array($file_ext, $deny_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 = '此文件类型不允许上传!';        }    } else {        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';    }}

没有对后缀名中的’::$DATA’进行过滤 在php+windows的情况下文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名 且保持"::$DATA"之前的文件名

少了 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA

image-20210706163343290

Pass-10 点空点绕过

源码分析

$is_upload = false;$msg = null;if (isset($_POST['submit'])) {    if (file_exists(UPLOAD_PATH)) {        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini");        $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        $file_ext = trim($file_ext); //首尾去空                if (!in_array($file_ext, $deny_ext)) {            $temp_file = $_FILES['upload_file']['tmp_name'];            $img_path = UPLOAD_PATH.'/'.$file_name;            if (move_uploaded_file($temp_file, $img_path)) {                $is_upload = true;            } else {                $msg = '上传出错!';            }        } else {            $msg = '此文件类型不允许上传!';        }    } else {        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';    }}

区别是路径里不是file_ext,而是file_name即保存文件的时候没有重命名而使用的原始的文件名 可以利用1.php. .(点+空格+点)来绕过

image-20210706163736964

Pass-11 双写绕过

源码分析

$is_upload = false;$msg = null;if (isset($_POST['submit'])) {    if (file_exists(UPLOAD_PATH)) {        $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess","ini");        $file_name = trim($_FILES['upload_file']['name']);        $file_name = str_ireplace($deny_ext,"", $file_name);        $temp_file = $_FILES['upload_file']['tmp_name'];        $img_path = UPLOAD_PATH.'/'.$file_name;                if (move_uploaded_file($temp_file, $img_path)) {            $is_upload = true;        } else {            $msg = '上传出错!';        }    } else {        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';    }}

str_ireplace把黑名单里的后缀全替换为空 但是这个函数只进行一次替换 所以可以双写绕过

image-20210706164422813

Pass-12 Get%00截断

源码分析

$is_upload = false;$msg = null;if(isset($_POST['submit'])){    $ext_arr = array('jpg','png','gif'); //数组    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);    /*返回字符串的子串substr(string $string, int $start, int $length = ?): string返回字符串 string 由 start 和 length 参数指定的子字符串。strrpos(string,find,start)函数查找字符串中最后一次出现的位置*/    if(in_array($file_ext,$ext_arr)){   //检查数组中是否存在某个值        $temp_file = $_FILES['upload_file']['tmp_name'];        $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;        if(move_uploaded_file($temp_file,$img_path)){            $is_upload = true;        } else {            $msg = '上传出错!';        }    } else{        $msg = "只允许上传.jpg|.png|.gif类型文件!";    }}

定义一个白名单,只允许三种文件类型

substr()函数返回字符的一部分,strrpos(string,find,start)函数查找字符串中最后一次出现的位置,这里是将后缀名提取出来赋值给file_ext img_path是直接拼接

我们可以抓包修改get的参数,然后通过file_ext无效,这样就可以上传PHP文件

%00截断的概念和原理:

在URL中%00表示ASCII码中的0,而0作为特殊字符保留,表示字符结束,当url中出现%00时就会认为读取已结束,而忽略后面上传的文件或图片,只上传截断前的文件或图片

要求:php版本小于5.3.4,php的magic_quotes_gpc为OFF状态 将路径改为path=“upload/1.php%00”,那么拼接之后,文件上传时就变成了“upload/1.php%1.jpg”,这时上传便将1.php上传进去,而1.jpg则被截断

切换php版本

image-20210706170624355

image-20210706170725171

Pass-13 Post%00截断

源码分析

$is_upload = false;$msg = null;if(isset($_POST['submit'])){    $ext_arr = array('jpg','png','gif');    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);    //拿出文件名后缀    if(in_array($file_ext,$ext_arr)){        $temp_file = $_FILES['upload_file']['tmp_name'];        $img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;        if(move_uploaded_file($temp_file,$img_path)){            $is_upload = true;        } else {            $msg = "上传失败";        }    } else {        $msg = "只允许上传.jpg|.png|.gif类型文件!";    }}

与上一题相比,区别是上传方式变成了POST。还是利用00截断

但是POST不会像GET对%00进行自动解码,需要在二进制中进行修改,将70 68 70后面的2b改为00

Pass-14 图片马绕过

源码分析

function getReailFileType($filename){  //得到真实的文件类型    $file = fopen($filename, "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'])){  //检测变量是否已设置并且非 null    $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 = "上传出错!";        }    }}- unpack(format,data)函数是规定在解包数据时所使用的格式,这里是文件头按照c格式解包- intval() 函数用于获取变量的整数值

rb是读取二进制文件

后面的fread函数只读两字节,也就是说只对文件头进行了检测

也就是说,文件头检测,如果你没有伪造文件头,即便你是jpg图片它也识别不出来,所以这里我们要抓包,伪造一个文件头

我们随便拿一张图片,文件内容最前面添加GIF89a 和

<?php $filename = $_GET['test']; include($filename); ?>

直接上传test.php文件,抓包拿到文件目录以及文件名

制作图片马 将一句话木马1.php和普通图片1.jpg合并 得到shell.jpg

这里Windows会报毒."无法成功完成操作,因为文件包含病毒或潜在的垃圾软件。"

要关掉防火墙里面的实时防护

dos命令: copy 1.jpg /b + 1.php /a shell.jpg

shell.jpg上传上去之后,拿到图片地址以及文件名

但直接访问图片并不能把图片当做PHP解析,还需要利用文件包含漏洞

在www/upload-labs/upload目录下建立一个php文件

<?php/*本页面存在文件包含漏洞,用于测试图片马是否能正常运行!*/header("Content-Type:text/html;charset=utf-8");$file = $_GET['file'];if(isset($file)){    include $file;}else{    show_source(__file__);}

image-20210707110607154

image-20210706184653956

Pass-15 图片马绕过

源码分析

function isImage($filename){    $types = '.jpeg|.png|.gif';  //三种类型的后缀    if(file_exists($filename)){  //文件存在文件名        $info = getimagesize($filename);  //getimagesize()取得图像大小        $ext = image_type_to_extension($info[2]);  //image_type_to_extension — 取得图像类型的文件后缀        if(stripos($types,$ext)>=0){    //stripos — 查找字符串首次出现的位置(不区分大小写)在变量types找,返回变量ext            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)){   //move_uploaded_file — 将上传的文件移动到新位置            $is_upload = true;        } else {            $msg = "上传出错!";        }    }}

用getimagesize函数获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息

仍然可以图片马绕过

Pass-16 图片马绕过

源码分析

function isImage($filename){  //    //需要开启php_exif模块    $image_type = exif_imagetype($filename);  //exif_imagetype — 判断一个图像的类型    switch ($image_type) {  //语句类似于具有同一个表达式的一系列 if 语句。很多场合下需要把同一个变量(或表达式)与很多不同的值比较,并根据它等于哪个值来执行不同的代码。        case IMAGETYPE_GIF:            return "gif";            break;        case IMAGETYPE_JPEG:            return "jpg";            break;        case IMAGETYPE_PNG:            return "png";            break;            default:            return false;            break;    }}$is_upload = false;$msg = null;if(isset($_POST['submit'])){  //检测变量是否已设置并且非 null    $temp_file = $_FILES['upload_file']['tmp_name'];    $res = isImage($temp_file); //在这里跑上去查看这个文件类型,返回一个后缀名或者错误    if(!$res){  //如果res==flase        $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 = "上传出错!";        }    }}

用到php_exif模块来判断文件类型

仍然可以图片马绕过

Pass-17 二次渲染绕过

源码分析

$is_upload = false;$msg = null;if (isset($_POST['submit'])){  ////检测变量是否已设置并且非 null    // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径    
$filename = $_FILES['upload_file']['name'];    $filetype = $_FILES['upload_file']['type'];    $tmpname = $_FILES['upload_file']['tmp_name'];    $target_path=UPLOAD_PATH.'/'.basename($filename);  //路径    // 获得上传文件的扩展名    
$fileext= substr(strrchr($filename,"."),1);  //strrchr — 查找指定字符在字符串中的最后一次出现 。substr — 返回字符串的子串。    //判断文件后缀与类型,合法才进行上传操作    
if(($fileext == "jpg") && ($filetype=="image/jpeg")){ //如果文件类型是jpg 后缀也是jpg        
if(move_uploaded_file($tmpname,$target_path)){  // 将上传的文件移动到新位置$target_path            //使用上传的图片生成新的图片            
$im = imagecreatefromjpeg($target_path);       //imagecreatefromjpeg — 由文件或 URL 创建一个新图象            
if($im == false){                
$msg = "该文件不是jpg格式的图片!";                
@unlink($target_path);  //unlink — 删除文件            
}
else{                //给新图片指定文件名                
srand(time());  //srand播下随机数发生器种子  time — 返回当前的 Unix 时间戳                
$newfilename = strval(rand()).".jpg"; //strval — 获取变量的字符串值                //显示二次渲染后的图片(使用用户上传图片生成的新图片)                
$img_path = UPLOAD_PATH.'/'.$newfilename;//路径                
imagejpeg($im,$img_path); //imagejpeg — 输出图象到浏览器或文件。                @unlink($target_path);//删除                $is_upload = true;            }        } else {            $msg = "上传出错!";        }//这里只只展示了jpg的情况
if(($fileext == "jpg") && ($filetype=="image/jpeg"))

检测$fileext$filetype是否为jpg格式.

if(move_uploaded_file($tmpname,$target_path)){

然后使用move_uploaded_file函数来做判断条件,如果成功将文件移动到$target_path,就会进入二次渲染的代码,反之上传失败.

在这里有一个问题,如果作者是想考察绕过二次渲染的话,在move_uploaded_file($tmpname,$target_path)返回true的时候,就已经成功将图片马上传到服务器了,所以下面的二次渲染并不会影响到图片马的上传.如果是想考察文件后缀和content-type的话,那么二次渲染的代码就很多余

由于在二次渲染时重新生成了文件名,所以可以根据上传后的文件名,来判断上传的图片是二次渲染后生成的图片还是直接由move_uploaded_file函数移动的图片.

在这里很明显,代码先将图片进行了上传,之后在进行判断格式是否正确,否则删除文件,所以这里存在一个逻辑漏洞

我们可以用bp不断的发送.php格式的包,一直发,然后我们在网页上浏览打开这个php文件,只要我们速度在代码执行删除之前打开了php文件,那么代码就不能对php文件进行任何操作了。

二次渲染绕过

https://xz.aliyun.com/t/2657

二次渲染了上传的图片

绕过方法:找到渲染前后没有变化的位置,然后将php代码写进去,就可以成功上传带有php代码的图片。

Pass-18 条件竞争

源码分析

$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);             $is_upload = true;        }else{            $msg = "只允许上传.jpg|.png|.gif类型文件!";            unlink($upload_file);        }    }else{        $msg = '上传出错!';    }}

先将文件上传到服务器,然后判断文件后缀是否在白名单里,如果在则重命名,否则删除。

这就在文件的处理顺序上出现了问题,不管文件类型是否合格就上传至服务器,之后再对其类型进行判断,这样的处理顺序导致了在多线程的情况下,有可能对于不合格的文件还没来得及删除就已经被访问,导致不合格的文件绕过了限制。

因此我们可以打个时间差:上传1.php,只需要在它删除之前访问即可。

可以利用burp的intruder模块不断上传,然后我们不断的访问刷新该地址。

image-20210707172250879

Pass-19 条件竞争

源码分析

//index.php$is_upload = false;$msg = null;if (isset($_POST['submit'])){    require_once("./myupload.php");    $imgFileName =time();    $u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);    $status_code = $u->upload(UPLOAD_PATH);    switch ($status_code) {        case 1:            $is_upload = true;            $img_path = $u->cls_upload_dir . $u->cls_file_rename_to;            break;        case 2:            $msg = '文件已经被上传,但没有重命名。';            break;         case -1:            $msg = '这个文件不能上传到服务器的临时文件存储目录。';            break;         case -2:            $msg = '上传失败,上传目录不可写。';            break;         case -3:            $msg = '上传失败,无法上传该类型文件。';            break;         case -4:            $msg = '上传失败,上传的文件过大。';            break;         case -5:            $msg = '上传失败,服务器已经存在相同名称文件。';            break;         case -6:            $msg = '文件无法上传,文件不能复制到目标目录。';            break;              default:            $msg = '未知错误!';            break;    }}//myupload.phpclass MyUpload{..................   var $cls_arr_ext_accepted = array(      ".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",      ".html", ".xml", ".tiff", ".jpeg", ".png" );..................    /** upload()   **   ** Method to upload the file.   ** This is the only method to call outside the class.   ** @para String name of directory we upload to   ** @returns void  **/  function upload( $dir ){        $ret = $this->isUploadedFile();        if( $ret != 1 ){      return $this->resultUpload( $ret );    }    $ret = $this->setDir( $dir );    if( $ret != 1 ){      return $this->resultUpload( $ret );    }    $ret = $this->checkExtension();    if( $ret != 1 ){      return $this->resultUpload( $ret );    }    $ret = $this->checkSize();    if( $ret != 1 ){      return $this->resultUpload( $ret );        }        // if flag to check if the file exists is set to 1        if( $this->cls_file_exists == 1 ){            $ret = $this->checkFileExists();      if( $ret != 1 ){        return $this->resultUpload( $ret );          }    }    // if we are here, we are ready to move the file to destination    $ret = $this->move();    if( $ret != 1 ){      return $this->resultUpload( $ret );        }    // check if we need to rename the file    if( $this->cls_rename_file == 1 ){      $ret = $this->renameFile();      if( $ret != 1 ){        return $this->resultUpload( $ret );          }    }        // if we are here, everything worked as planned :)    return $this->resultUpload( "SUCCESS" );    }.................. };

白名单,一步一步检查文件大小、文件是否存在等等。

move在rename之前,move操作进行了一次文件保存,rename进行了一次更改文件名。

因此可以通过不断上传图片马,由于条件竞争可能来不及重命名。

image-20210707173546407

Pass-20 多种方法

源码分析

$is_upload = false;$msg = null;if (isset($_POST['submit'])) {    if (file_exists(UPLOAD_PATH)) {        $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");        $file_name = $_POST['save_name'];        $file_ext = pathinfo($file_name,PATHINFO_EXTENSION);        if(!in_array($file_ext,$deny_ext)) {            $temp_file = $_FILES['upload_file']['tmp_name'];            $img_path = UPLOAD_PATH . '/' .$file_name;            if (move_uploaded_file($temp_file, $img_path)) {                 $is_upload = true;            }else{                $msg = '上传出错!';            }        }else{            $msg = '禁止保存为该类型文件!';        }    } else {        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';    }}

这关感觉像是个测试,对比前面可以发现有很多方法

点绕过或空格绕过:直接在后缀名后面加上/.

利用 apache 的解析漏洞,如给文件命名为test.php.aba

Post的00截断

Pass-21 来自ctf

源码分析

$is_upload = false;$msg = null;if(!empty($_FILES['upload_file'])){    //检查MIME    $allow_type = array('image/jpeg','image/png','image/gif');    if(!in_array($_FILES['upload_file']['type'],$allow_type)){        $msg = "禁止上传该类型文件!";    }else{        //检查文件名        $file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];        if (!is_array($file)) {            $file = explode('.', strtolower($file));        }        $ext = end($file);        $allow_suffix = array('jpg','png','gif');        if (!in_array($ext, $allow_suffix)) {            $msg = "禁止上传该后缀文件!";        }else{            $file_name = reset($file) . '.' . $file[count($file) - 1];            $temp_file = $_FILES['upload_file']['tmp_name'];            $img_path = UPLOAD_PATH . '/' .$file_name;            if (move_uploaded_file($temp_file, $img_path)) {                $msg = "文件上传成功!";                $is_upload = true;            } else {                $msg = "文件上传失败!";            }        }    }}else{    $msg = "请选择要上传的文件!";}

in_array(value,array,type) 在数组中搜索给定的值

参数 描述
value 必需。规定要在数组搜索的值
array 必需。规定要搜索的数组。
type 可选。如果设置该参数为true,则检查搜索的数据与数组的值的型昰否相同

explode(separator,string,limit) 把字符串打散为数组

参数 描述
separator 必需。规定在哪里分割字符串。
string 必需。要分割的字符串。
limit 可选。规走所返回的数组元素的数目。

白名单限制

对文件上传的文件类型做了判断 将用户输入的文件名赋值给 $file is_array($file) 判断文件名是不是数组,如果不是,使用 explode() 以 . 为标记分割为数组 如果是数组,则定义后缀名白名单 $allow_suffix 对数组最后一个元素 $ext(正常情况是指后缀名)进行过滤 如果符合则取数组的第一个元素reset($file)作为文件名,倒数第二个元素$file[count($file)-1]作为后缀名。

绕过方法

$file[0]为smi1e.php/,也就是reset($file) $file[2]为白名单中的jpg,此时end($file)等于jpg,$file[count($file) - 1]为空 而$file_name = reset($file) . '.' . $file[count($file) - 1];,也就是smi1e.php/. 于是move_uploaded_file会忽略掉/.,最终上传smi1e.php

本文作者:SirCat, 转自FreeBuf