
upload-labs 文件上传漏洞通关记录
upload-labs
是一个使用php
语言编写的,专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。旨在帮助大家对上传漏洞有一个全面的了解。目前一共21关,每一关都包含着不同上传方式。
运行环境
博主使用的PHPstudy apache+php5.2(扩展:php_gd2,php_exif),自建容易有坑,建议直接下载releases方便快速搭建。
第1关
直接上传,弹窗了 盲猜js验证 直接禁用js上传成功。
也可以burp抓包修改文件类型(上传webshell.jpg
修改为webshell.php
)。


第2关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| $is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { 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'] if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '文件类型不正确,请重新上传!'; } } else { $msg = UPLOAD_PATH.'文件夹不存在,请手工创建!'; } }
|
直接burp抓包把application/octet-stream
修改为允许的类型image/jpeg、image/png、image/gif
即可

也可以用第一关修改后缀的方法上传。
第3关
源码直接写死了禁止上传.asp .aspx .php .jsp
这四种后缀的文件 这里百度了一番 直接修改后缀为phtml
。
- Apache的解析顺序是从右到左开始解析文件后缀的,如果最右侧扩展名不可识别,就继续往左判断。直到遇到可以解析的文件后缀为止
- 可以上传例如
php3, phtml
后缀的文件绕过,前提是Apache的httpd.conf中配置有如下代码:
AddType application/x-httpd-php .php .php3 .phtml


第4关
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
| $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']); $file_name = deldot($file_name); $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); $file_ext = str_ireplace('::$DATA', '', $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.'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件不允许上传!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
|
这题基本把各种后缀全禁止了 但没有限制.htaccess
.htaccess
文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过.htaccess
文件,可以实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能IIS平台上不存在该文件,该文件默认开启,启用和关闭在httpd.conf
文件中配置。
因此我们可以上传一个.htaccess
文件内容:SetHandler application/x-httpd-php
这样所有文件都会被当成PHP来进行解析 接着我们直接上传个图片马。

第5关
黑名单里多了个.htaccess
少了个ini
创建一个.user.ini
文件, 内容如下:
1
| auto_prepend_file=123.gif
|
将.user.ini
上传到服务器, 然后将包含一句话的php文件
重命名为123.gif
, 上传到服务器
然后访问upload/readme.php
会自动包含并解析123.gif
文件
第6关
查看源码发现后缀全限制死了 但是发现少了一行$file_ext = strtolower($file_ext); //转换为小写
直接后缀大小写混合 绕过上传成功。

第7关
查看源码发现少了$file_ext = trim($file_ext); //首尾去空
直接后缀后面加个空格绕过。

第8关
查看源码发现这题少了$file_name = deldot($file_name);//删除文件名末尾的点
利用windows特性,会自动去掉后缀名中最后的.
,直接后缀后面加.
即可绕过。

第9关
查看源码发现少了$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
在Windows+PHP的情况下:如果文件名+::$DATA
,::$DATA
之后的数据当成文件流处理,不会检测后缀名 且保持”::$DATA
“之前的文件名。例如webshell.php::$DATA
变为 webshell.php
直接后缀加上::$DATA
绕过 上传成功 访问上传的文件是注意去掉地址最后的::$DATA

第10关
查看源码后,发现第15行
拼接的是$file_name
而不是$file_ext
,而$file_name
只处理了文件名末尾的点
所以我们可以输入. .
来绕过。
这题自我感觉有点绕 写个解析 以备参考。
第6行
使用trim
先去除了文件前后的空格,第7行
使用deldot
去除了文件名末尾的.
第8行
使用strrchr
来寻找.
来确定文件的后缀
此时请我们输入webshell.php. .
(点空格点)会匹配为.
(点空格) 从而达到绕过的目的。

第11关
源码第8行
使用str_ireplace
进行了匹配替换 如果存在黑名单内的内容 则替换为空且只替换一次例如webshell.php
替换为webshell.
我们可以通过双写绕过

第12关
源码第8行
进行了一次GET传参 修改一句话的后缀名为.jpg
然后修改URL后直接使用%00
url终止符进行截断。成功上传访问时记得删除PHP后面的内容
截断条件:PHP版本小于5.3.4,PHP的magic_quotes_gpc为OFF状态 否则magic_quotes_gpc会在敏感字符前加上反斜杠(\)

第13关
和上一关唯一的区别就是GET
变为了POST
我们使用00截断


第14关
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 39 40
| function getReailFileType($filename){ $file = fopen($filename, "rb"); $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 = "上传出错!"; } } }
|
查看源码后,第3行
读取上传文件的前两个字节,接着使用unpack
解码后,intval
转换为10进制,根据转换后的结果进行case
CMD生成图片马
1
| copy webshell.jpg /b + webshell.php /a shell.jpg
|
shell.jpg
就是生成的图片马 直接上传即可 直接访问图片并不能把图片当做PHP解析 利用文件包含解析图片马里的PHP,file为我们的图片马位置。
1 2 3 4 5 6 7 8 9 10 11 12
| <?php
header("Content-Type:text/html;charset=utf-8"); $file = $_GET['file']; if(isset($file)){ include $file; }else{ show_source(__file__); } ?>
|

第15关
同上一关 直接用上一关的图片马上传即可
查看源码 第4行
使用getimagesize
获取图片类型,打印一下返回了一个数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| array(7) { [0]=> //图像宽度像素值 int(50) [1]=> //图像高度像素值 int(52) [2]=> //图像类型,1=GIF,2=JPG,3=PNG,4=SWF,5=PSD,6=BMP,7=TIFF_II,8=TIFF_MM,9=JPC,10=JP2,11=JPX,12=JB2,13=SWC,14=IFF,15=WBMP,16=XBM,17=ICO,18=COUNT int(2) [3]=> //图像宽度和高度的字符串 string(22) "width="50" height="52"" ["bits"]=> //图像的每种颜色的位数,二进制格式 int(8) ["channels"]=> //图像的通道值 int(3) ["mime"]=> //图像的MIME信息 string(10) "image/jpeg" }
|
第16关
这里用的php_exif
扩展来判断的上传的文件类型 同上一关 直接用上一关的图片马上传即可
第17关
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| $is_upload = false; $msg = null; 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 = "上传出错!"; }
}else if(($fileext == "png") && ($filetype=="image/png")){ if(move_uploaded_file($tmpname,$target_path)){ $im = imagecreatefrompng($target_path);
if($im == false){ $msg = "该文件不是png格式的图片!"; @unlink($target_path); }else{ srand(time()); $newfilename = strval(rand()).".png"; $img_path = UPLOAD_PATH.'/'.$newfilename; imagepng($im,$img_path);
@unlink($target_path); $is_upload = true; } } else { $msg = "上传出错!"; }
}else if(($fileext == "gif") && ($filetype=="image/gif")){ if(move_uploaded_file($tmpname,$target_path)){ $im = imagecreatefromgif($target_path); if($im == false){ $msg = "该文件不是gif格式的图片!"; @unlink($target_path); }else{ srand(time()); $newfilename = strval(rand()).".gif"; $img_path = UPLOAD_PATH.'/'.$newfilename; imagegif($im,$img_path);
@unlink($target_path); $is_upload = true; } } else { $msg = "上传出错!"; } }else{ $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!"; } }
|
判断了文件后缀与类型 利用imagecreatefromgif
判断是否为gif
图片,最后再做了二次渲染,具体可以参考 upload-labs Pass17
第18关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| $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 = '上传出错!'; } }
|
这里是条件竞争 先将文件上传到服务器第9行
拼接了文件上传后的路径,第11行
使用move_uploaded_file
把上传的文件移动到临时目录 然后判断文件是否在白名单内 在的话使用rename
进行重命名,否则使用unlink
删除文件 这里我们直接赶在删除前 去访问文件 写入一个shell就行了 这里可以使用burp的Intruder不断上传文件,然后我们不断的访问刷新该地址。
1
| <?php fputs(fopen('webshell.php','w'),'<?php phpinfo(); ?>');?>
|

第19关
看了下upload-labs的介绍 Pass-19必须在linux下运行,直接上传了图片马 然后利用前几关的文件包含漏洞…
第20关
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
| $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 . '文件夹不存在,请手工创建!'; } }
|
这题给了黑名单 但是能自定义上传的文件名,我们直接用前几关的知识绕过就行了 文件名后面加个空格/点/::$DATA都可以。
第21关
先检查Content-type
,接着检查文件名,保存名称为空的就用上传的文件名,接着!is_array
判断文件名是否为数组,不是的话就使用explode
通过.
号分割成数组。然后获取后缀名,进行白名单验证。符合条件就拼接数组的第一个和最后一个作为文件名,保存。
具体参考:https://blog.csdn.net/Fly_hps/article/details/99660226
写在后面
增加了大量奇怪的上传姿势,也增加了防御的各种思路。部分题参考了网上的题解,其他差不多都是用burp
的重发器和var_dump()
打印函数慢慢调的 有几题没做出来 学习中…