PHP语言编写,持续收集渗透测试和CTF中针对文件上传漏洞的靶场,总共21关,每一关都包含着不同的上传绕过方式。
file:要移动的文件
newloc:规定文件的位置
search:规定在数组中搜索的值
array:数组
上传限制演示
》》判断站点开发语言(PHP,也可以参考开发语言的其它判断方法)
(我们先通过操作和抓取数据包方式走一边正常的上传流程)
》》正常上传一图片文件上传成功
》》上传成功,服务器的upload目录下会存放上传的对应文件(操作过程中用户看不到此目录,这里用于理解)
》》前端会和后端进行交互,抓取的上传数据包流量
》》我们上传一个小马文件,提示该文件不允许上传,需要特定后缀类型文件
源码分析
》》通过上边源码和网页源代码可看到JS脚本,判断使用文件类型在前端进行了限制
》》因此只要绕过了前端的JS校验就可以直接提给后端
绕过方法
现在小马文件已经上传至服务器,现在我们拿到小马的执行路径就完好了(后端允许解析执行的情况下)
》》通过浏览器检查页面元素即可拿到上传文件的相对路径(->网页右击 ->检查)
》》也可以通过返回数据包中查看上传的地址,这两个一样的
》》拼接得到上传文件的主目录
Q&A
Q1:只要上传完文件都会在返回包中拿到文件的相对/绝对路径吗?
A1:非也,需要看开发人员安全意识是否足够,后端代码中是否回显出路径
Q2:怎么通过返回的相对路径拼接成文件的绝对路径?
A2:根据经验!
》》如这里返回的绝对路径为:
》》暂且将返回的路径和当前地址的路径copy到编辑器中
》》对网页目录进行分析》》通过点击不同的网页目录,我们发现绿色部分是不变/相同的,当我们点击目录时只有后边的内容在变,因此可判断upload-labs是站点www目录下的主目录文件
(网站站点目录和URL地址图)
(上传文件地址和. ./上级目录图)
Q3:如何判断上传的脚本被成功解析?
A3:脚本被解析代码会运行!
》》我们在站点目录下新建一个普通文本的文件
》》浏览器访问php文件会被解析执行因此什么都不显示
判断技巧
查看网页源代码是否有限制的JS代码,点击上传后直接弹框提示,但是没有发送数据包,即用了客户端认证方法
上传限制演示
》》上传一句话,提示文件类型不正确
(上传数据包的MIME类型)
Tips:能抓到数据包,证明不是前端JS校验,是服务端进行校验
绕过方法一
》》上传一合格的图片文件,记录其MIME类型
Content-Type: image/jpeg
》》上传一句话木马文件,点击上传时抓包修改文件的MIME类型为图片的MIME类型即可
上传限制演示
》》上传一图片文件
》》可抓取数据包,返回的相对路径
》》上传一php文件,提示不允许上传如下文件
》》上传图片文件改后缀改MIME类型皆报错
绕过方法
后端采用了黑名单机制,只要不在黑名单中的后缀即可绕过
》》上传一php文件,抓取数据包,对文件后缀进行遍历绕过(添加至intruder模块)
》》导入后缀文件
》》依次查看不同的文件后缀中哪些文件能上传
》》点击任意一个可上传的文件浏览器查看未被解析
(条件利用苛刻需要开启SSI)
如绕过中,服务器需对绕过的文件进行解析,其apache配置文件httpd.conf中有如下配置(这里为了演示手动添加!)
》》重启服务
上传限制演示
限制:
- 黑名单中未过滤 .htaccess
- apache配置文件httpd.conf中启用了AllowOverride All
- 服务端未处理文件名(否则上传到htaccess到目录下成为其它文件名则不奏效)
》》上传一图片文件,成功
(返回的路径信息)
》》上传一php文件,提示此文件不允许上传
绕过方法
》》由于上传的文件名未经过处理,这里使用.htaccess文件攻击
Tips:.htaccess:apache服务器配置文件,负责相关网页针对每个目录下的配置,即在一个特定目录下放置一个包含指令的文件,其中的指令作用于此目录及其所有子目录。(这里我们利用这个未过滤的文件来上传一个修改后缀的php文件为jpg,然后让服务器去解析)
》》未对.htaccess文件进行限制
》》上传一1.htaccess文件,文件内容如下
(将上传的文件后缀名为.jpg格式的文件以php格式来解析)
AddType application/x-httpd-php .jpg
》》点击上传,抓包修改文件名为 .htaccess
》》上传成功(客户端显示)
(服务器目录下显示)
》》将php文件后缀由php改为jpg上传,上传成功
》》服务器访问成功被解析
》》菜刀成功连
上传限制演示
》》上传一图片文件成功,并返回文件的相对路径
》》上传一php文件
提示此文件类型不允许上传
源码分析
》》查看源码
依旧采用黑名单方式,过滤了文件后缀的点,和空格,且将.htaccess加入黑名单
绕过方法
(这里”投其所好“,双写点和空格绕过)
》》上传一php文件,抓包修改后缀,上传成功
上传限制演示
》》上传一正常照片文件,上传成功返回相对路径
》》上传php文件,显示此类型不允许上传
》》依旧采用黑名单方式且过滤掉了.htaccess文件
绕过方法
上传限制演示
》》上传正常图片文件,上传成功并返回相对路径
》》上传php文件,显示如下
源码分析
》》缺少对首尾去空校验
$file_ext = trim($file_ext); //首尾去空
上传限制演示
》》上传正常的图片,返回相对路径
》》上传php文件
》》禁止上传所有可解析后缀
绕过方法
Tips:利用Windows下文件后添加的点会自动忽略
上传限制演示
》》上传普通的图片文件,上传成功返回相对路径
》》上传php文件,显示不允许上传
源码分析
(这里未使用str_ireplace()替换字符函数去除 ::$DATA 字符)
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
绕过方法
Tips:采用Windows流特性绕过,在这里意思是php运行在Windows上时如果文件名+":: D A T A " 会 把 : : DATA"会把:: DATA"会把::DATA之后的数据当作文件流处理,不会检测后缀名,且保持“::$DATA”之前的文件名,目的即使不检查后缀名
》》上传php文件,修改文件后缀添加::$DATA,即可上传成功
》》菜刀文件管理查看上传的文件
上传限制演示
》》上传正常的图片文件
》》上传php文件
》》提示
源码分析
(这里黑名单严格、删除文件名末尾点、大小写转换为小写、去除::$DATA字符、首尾去空)
绕过方法
Tips:代码逻辑绕过(这里代码逻辑先删除文件名末尾点,再首尾去空都只进行一次,因此可构造点空格点绕过。利用windows特性)
》》上传php文件,修改后缀如下即可绕过
》》站点目录下的上传文件
Tips:从第三关到第十关目标服务器是Windows的话皆可用此方法绕过
上传限制演示
》》上传正常的图片文件,上传成功并返回图片相对路径
》》上传php文件,上传能成功但是文件后缀被切掉了
》》 关卡提示:去除掉上传文件名中以下字符
源码分析
(通过str_ireplace()函数将黑名单中的文件后缀名进行了替换,换为空字符)
》》因此上传的文件名在站点下变成了:
绕过方法
str_ireplace() 函数不区分大小写,因此大小写绕过不适用,这里我们使用双写文件名绕过
》》抓取上传的php文件,双写文件名(和xss双写绕过类似),上传成功
》》成功解析
上传限制演示
》》上传一正常的图片文件,上传成功
》》上传一php文件
》》查看提示
源码分析
这里用了白名单进行限制,只允许上传jpg/png/gif文件格式,拼接的文件路径为随机数+时间+文件后缀。构造文件存储路径使用GET传入,导致服务器最终存储的文件名可控。
绕过方法
》》使用00截断绕过
Tips:条件
》》将php后缀文件改为服务器允许上传的文件后缀,上传成功
将123.php后的内容忽略掉,使用common_php.jpg文件中的内容上传到123.php中
》》上传正常的图片文件,上传成功返回文件相对路径
》》上传php文件,对文件后缀进行限制
源码分析
》》这里的save_path是通过POST传参过来
绕过方法
00截断绕过,POST不会像GET自动解码,因此这里需要对进行编码
》》将php文件后缀改为jpg,上传抓包,123.php后加一个空格(16进制为20):
》》16进制查看修改,将空格20改为00(70后边)
(改完如下显示:)
》》上传成功
》》成功解析
Tips:和DVWA 文件上传 high类似
源码分析
(先读文件前两个字节,通过对比文件头来确定文件类型,即只对文件头检测)
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'])){
$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 = "上传出错!";
}
}
}
绕过方法
通过将一句话包含到图片文件中,上传图片马到目标站点下,通过文件包含漏洞执行php代码
》》制作图片马
(common_php.php文件内容)
fputs(fopen("hack.php","w"),"") ?>
》》一句话包含隐匿于图片马中
》》上传图片马,上传成功,拿到返回的相对路径(如果直接访问相对路径只是一张照片并不能解析php语句)
》》站点目录下的上传对应文件
使用文件包含访问图片马(站点需存在文件包含漏洞才可利用此漏洞,这里我使用同站点下的DVWA靶场的文件包含漏洞进行的演示)
》》使用同站点下的DVWA靶机文件包含爆出站点绝对路径
》》通过文件包含爆出的站点路径结合上传给的相对路径,构造访问一句话木马文件
》》hack.jpg会运行hack.php中的代码在文件包含目录下生成hack.php一句话文件
》》文件包含目录成功解析此文件
》》菜刀连接此地址
http://192.168.226.130/DVWA/vulnerabilities/fi/hack.php
》》上传正常的图片文件,返回站点的相对路径
》》上传php文件,报错
》》查看提示
源码分析
》》使用 getimagesize()函数 验证文件头信息
绕过方法
》》生成图片马
》》将图片马上传到服务器站点下
》》使用DVWA的文件包含解析此文件
》》爆出站点路径
》》文件包含执行此文件
》》查看生成的一句话是否能解析
源码分析
(使用exif_imagetype()函数来判断是否为图片文件)
绕过方法
图片马绕过
上传限制演示
》》上传图片马尝试,上传成功
》》使用文件包含访问该文件,发现文件中并没有我们写入的php代码
源码分析
(对文件的类型进行判断,再使用imagecreatefromjpeg|gif|png判断是否为图片文件,是的话对其进行二次渲染)
if (isset($_POST['submit'])){
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$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);
//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);
if($im == false){
$msg = "该文件不是jpg格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagejpeg($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
绕过方法-gif
》》首先制作gif图片的图片马
》》将文件上传到服务器
》》上传到服务器的照片另存到本地
》》通过16进制编辑器寻找和图片马文件相同的地方
》》蓝色位置是相同的
》》在相同的位置插入php代码
上传限制演示
》》上传一正常图片
》》上传php文件
》》查看提示
源码分析
(先使用move_uploaded_file()将上传的文件临时保存到服务器,再进行判断,如果在数组中就保存重命名,如果不在就直接删除)
$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 = '上传出错!';
}
}
绕过方法
(这里可以通过条件竞争方式进行绕过)
》》使用Burp的Intruter模块重放
(设置访问请求)
》》浏览器构造url进行访问
》》设置线程和访问,然后观察响应即可
绕过方法
程序来不及rename时通过竞争条件上传shell
上传限制演示
》》上传图片文件,并自定义保存文件名称
》》上传成功并生成文件路径
》》上传php文件,定义文件名为php
》》查看提示
源码分析
(定义一个数组,使用pathinfo()函数提取文件后缀,和白名单中对比,如果不在其中则返回文件路径)
$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 . '文件夹不存在,请手工创建!';
}
}
绕过方法1 - 图片马+文件包含
》》生成图片马(phpinfo)
》》将图片马上传到站点
》》文件包含进行访问
(move_uploaded_file会自动忽略文件末尾的/.因此可构造保存路径为1.php/.这样file_ext的值即可空这样便可绕过黑名单)
(windows中文件不允许加/ \ 因此自动删掉)
》》上传一句话文件,使用 /. 进行绕过
》》上传成功
》》利用了windows特性结合move_uploaded_file()成功处理/.的问题
》》成功解析
上传限制演示
》》上传一正常图片文件,上传成功
》》上传php脚本文件
》》该关卡提示代码审计
源码分析
(首先使用白名单判断文件MIME类型必须为数组中的图片类型; 检查文件名如果不是数组使用explode分割成数组; 提取上传文件名的最后一后缀,再次白名单判断保存文件名是否为图片; 文件名返回数组中第一个元素并输出文件名,如果没有定义数组的话则输出后缀;)
$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 = "请选择要上传的文件!";
}
绕过方法
(这里我们可以定义一个数组,利用如下代码构造数组来绕过)
$file_name = reset($file) . '.' . $file[count($file) - 1];
》》正常上传一句话(这里需要将一句话改成图片文件来绕过MIME校验)
》》抓取数据包,构造数组如下成功上传
Q&A
Q:为何这里使用数组save_name[2]那
A:由后台站点代码可知:$file_name = reset($file) . '.' . $file[count($file) - 1];
count(file)=2
$file[count(file)-1]=1,而
$file[1]为空,所以
$file_name=upload-21.php