iami233
iami233
文章156
标签37
分类4

文章分类

文章归档

upload-labs 文件上传漏洞通关记录

upload-labs 文件上传漏洞通关记录

upload-labs 是一个使用 php 语言编写的,专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。旨在帮助大家对上传漏洞有一个全面的了解。目前一共21关,每一关都包含着不同上传方式。

运行环境

博主使用的 PHPstudy + apache+php5.2 (扩展: php_gd2 , php_exif),自建容易有坑,建议直接下载 releases 方便快速搭建。

第1关

直接上传,弹窗了。盲猜js验证 直接 禁用js 上传成功。

也可以burp抓包修改文件类型(上传 webshell.jpg 修改为 webshell.php)。

image-20210223222306415

image-20210223220706651

第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)) {
// 判断Content-type是否为以下类型 image/jpeg、image/png、image/gif
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.'文件夹不存在,请手工创建!';
}
}

直接抓包把 application/octet-stream 修改为允许的类型 image/jpeg、image/png、image/gif 即可

image-20210223221238723

也可以用第一关修改后缀的方法上传。

第3关

源码直接写死了禁止上传 .asp .aspx .php .jsp 这四种后缀的文件,这里百度了一番,直接修改后缀为 phtml

  • Apache的解析顺序是从右到左开始解析文件后缀的,如果最右侧扩展名不可识别,就继续往左判断。直到遇到可以解析的文件后缀为止
  • 可以上传例如 php3, phtml 后缀的文件绕过,前提是 Apachehttpd.conf 中配置有如下代码:
    1
    AddType application/x-httpd-php .php .php3 .phtml

image-20210223223312675

image-20210223223440012

第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);//去除字符串::$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

.htaccess 文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。

通过 .htaccess 文件,可以实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能IIS平台上不存在该文件,该文件默认开启,启用和关闭在 httpd.conf 文件中配置。

因此我们可以上传一个 .htaccess 文件内容:

1
SetHandler application/x-httpd-php

这样所有文件都会被当成PHP来进行解析 接着我们直接上传个图片马。

image-20210223224256529

第5关

黑名单里多了个 .htaccess 少了个 ini

创建一个 .user.ini 文件, 内容如下:

1
auto_prepend_file=123.gif

.user.ini 上传到服务器, 然后将包含一句话的 php文件 重命名为 123.gif, 上传到服务器

然后访问 upload/readme.php 会自动包含并解析 123.gif 文件

第6关

查看源码发现后缀全限制死了 但是发现少了一行

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

直接后缀大小写混合 绕过上传成功。

image-20210223230623317

第7关

查看源码发现少了

1
$file_ext = trim($file_ext); //首尾去空

直接后缀后面加个空格绕过。

image-20210223231126124

第8关

查看源码发现这题少了

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

利用 windows 特性,会自动去掉后缀名中最后的 .,直接后缀后面加 . 即可绕过。
image-20210223231440263

第9关

查看源码发现少了

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

Windows + PHP 的情况下:如果 文件名 + ::$DATA,::$DATA 之后的数据当成文件流处理,不会检测后缀名,且保持 ::$DATA 之前的文件名。

例如 webshell.php::$DATA 变为 webshell.php

直接后缀加上 ::$DATA 绕过上传成功,访问上传的文件是注意去掉地址最后的 ::$DATA

image-20210223232126890

第10关

查看源码后,发现 第15行 拼接的是 $file_name 而不是 $file_ext,而 $file_name 只处理了 文件名末尾的点 所以我们可以输入 . . 来绕过。

这题自我感觉有点绕 写个解析 以备参考。

第6行 使用 trim 先去除了文件前后的空格,第7行 使用 deldot 去除了文件名末尾的 .第8行 使用 strrchr 来寻找 . 来确定文件的后缀

此时请我们输入 webshell.php. . (点空格点) 会匹配为 . (点空格) 从而达到绕过的目的。

image-20210223233057946

第11关

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

image-20210223235759186

第12关

源码 第8行 进行了一次GET传参 修改一句话的后缀名为 .jpg 然后修改URL后直接使用 %00 url终止符进行截断。成功上传访问时记得删除PHP后面的内容

截断条件:PHP版本小于5.3.4,PHP的magic_quotes_gpc为OFF状态 否则magic_quotes_gpc会在敏感字符前加上反斜杠(\)

image-20210224000947285

第13关

和上一关唯一的区别就是 GET 变为了 POST

我们使用00截断

img

img

第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); //只读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__);
}
?>

image-20210224003601737

第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(); ?>');?>

image-20210224165225438

第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() 打印函数慢慢调的 有几题没做出来 学习中…

本文作者:iami233
本文链接:https://5ime.cn/upload-labs.html
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可