文件上传漏洞-学习笔记-2

KaldX

一、 理解请求与服务器行为

  1. multipart/form-data 请求结构剖析:

    • 边界 (Boundary): Content-Type 头中定义,用于分隔请求体中的不同部分。抓包时留意这个边界字符串。
    • 部分 (Part): 每个表单字段(包括文件)是一个独立的部分。
    • Content-Disposition: 包含 name (表单字段名) 和 filename (原始文件名)。可伪造
    • Content-Type (部分内): 每个部分(尤其是文件部分)可以有自己的 Content-Type可伪造
    • 数据: 文件的原始二进制内容。
    • 利用点: 熟悉此结构是使用 Burp Suite 等工具手动修改请求、伪造文件名/类型、插入特殊字符(如 00 截断)的基础。
  2. PHP 后端处理流程 ($_FILES):

    • 文件首先被 PHP 接收并存储在临时目录 (upload_tmp_dir,通常是 /tmp),文件名是随机的 (如 phpXXXXXX)。
    • $_FILES 超全局数组包含上传文件的信息:name (原始名), type (客户端报告的 MIME), tmp_name (服务器上的临时文件路径), error (错误码), size (大小)。
    • 关键函数: move_uploaded_file($tmp_name, $destination) 是将临时文件移动到最终位置的标准方式。它比 copy() + unlink() 更安全,因为它会检查 $tmp_name 是否真的是一个合法的上传文件。
    • 让恶意文件内容通过检查,并最终被 move_uploaded_file (或其他文件写入函数) 移动到 Web 目录下,且文件名是我们期望的可执行后缀。
  3. 临时文件的捕获与分析 (/tmp):

    • 时机: 临时文件只在 PHP 脚本处理上传期间存在,脚本结束即被删除。
    • 工具 (inotifywait - Linux):
      1
      2
      # 实时监控 /tmp 下 php 开头的临时文件创建事件
      sudo inotifywait -m /tmp -e create -e moved_to --format '%w%f' | grep --line-buffered '/tmp/php[a-zA-Z0-9]\{6\}'
    • 目的: 在复杂场景下,观察临时文件的确切内容、权限、以及它在被处理(如二次渲染、内容检查)前后的变化,有助于理解服务器的防御机制。看到文件名立刻 cpcat

二、 绕过补充

  1. 前端绕过:

    • 工具: 浏览器开发者工具直接修改 HTML accept 属性或 JS;Burp Suite/OWASP ZAP 拦截并修改请求;curl 命令直接构造请求。
    • curl 示例 (伪造类型和文件名):
      1
      curl -X POST -F "file=@shell.php;type=image/jpeg;filename=legit.jpg" http://target.com/upload
  2. .htaccess 利用 (Apache):

    • 前提: 目标目录 Apache 配置允许 AllowOverride (至少 FileInfoOptions)。
    • 核心指令:
      • AddType application/x-httpd-php .jpg: 让 .jpg 按 PHP 执行。
      • SetHandler application/x-httpd-php: 让目录下所有文件按 PHP 执行。
      • php_flag engine off: 尝试关闭 PHP 引擎(如果允许)。
      • php_value auto_prepend_file /path/to/shell.php: 包含文件后门。
    • 测试: 在本地 phpStudy (Apache 模式) 中修改目录的 AllowOverride 配置并重启服务来验证 .htaccess 是否生效。
  3. .user.ini 利用细节 (PHP CGI/FPM):

    • 前提: PHP 以 CGI/FPM 模式运行 (Nginx+PHP-FPM, Apache+PHP-FPM 常见)。
    • 核心指令: auto_prepend_file = shell.jpg (在执行 PHP 前包含)。
    • 触发条件: 目标目录下必须存在一个可被访问的 .php 文件(内容无所谓,仅作触发器)。
    • 缓存: 默认有 300 秒缓存 (user_ini.cache_ttl),上传后需等待或尝试多次访问触发文件。
    • 攻击流程: 上传 shell.jpg -> 上传 .user.ini (内容指向 shell.jpg) -> 访问该目录下的任意 .php 文件 -> 触发 shell.jpg 执行。
  4. 00 截断关键点:

    • 版本: 主要影响 PHP < 5.3.4
    • 配置: 需要 magic_quotes_gpc = Off
    • 原理: 利用 C 语言字符串以 \0 结尾的特性,在传递给底层文件系统函数时截断路径或文件名。
    • GET vs POST: GET 请求中的 %00 会被自动解码;POST 请求中需在 Burp 等工具的 Hex 视图中手动将 %00 的 URL 编码 (25 30 30) 改为实际的空字节 (00)。
  5. 文件头绕过与内容检测对抗:

    • 图片马: GIF89a + <?php ... ?>
    • 对抗 getimagesize()/exif_imagetype(): 这些函数通常只检查文件头和少量元数据。只要文件头合法,后面跟代码通常能过。
    • 对抗 GD/Imagick (二次渲染): 绕过需要构造能在渲染/压缩后仍保留恶意代码的特殊图片。
      • 触发良性错误: 主要用于信息收集和 DoS,不太可能直接 RCE。GD 库(以及 Imagick)在尝试加载和解析图像文件时,如果文件结构严重损坏或不符合规范,对应的 imagecreatefromXXX 函数会失败并返回 false,同时通常会触发一个 PHP Warning 或 Notice(如果服务器开启了错误显示 display_errors 或记录日志 log_errors)。
      • 利用库的 CVE 漏洞: 如果成功,可以在图片被“处理”的瞬间就获得代码执行权限。
  6. 条件竞争 (Race Condition) 利用:

    • 识别: 寻找“先移动/保存,后检查/删除”的代码逻辑。
    • 利用: 上传文件后,立即高并发地访问目标文件 URL。使用多线程脚本(如 Python requests + threading)提高成功率。
    • 变种: 上传“写入器” (Writer Shell),高并发访问写入器,让它在被删除前创建最终的 Webshell。
  7. file_put_contents() vs unlink()

    • 虽然利用文件句柄阻止 unlink 在 Web 场景下很难稳定复现,但可以关注权限问题。如果在 file_put_contents 后、unlink 前,能通过其他漏洞改变文件或目录权限,使 Web 进程无法删除,则可持久化。
  8. PHP 可解析标签绕过 WAF:

    • 如果 WAF 只检测 <?php,而服务器开启了 short_open_tag=On,则 <? ... ?> 可绕过。
    • <% ... %><script language="php"> 在 PHP 7 已移除,但老系统可能存在。
  9. 命令执行函数选择:
    * 需要看回显,且回显简单:system() (直接输出 + 最后一行返回)。
    * 需要完整回显,自己处理:exec() (存入数组) 或 shell_exec()/反引号 (返回完整字符串)。
    * 需要原始/二进制输出:passthru()
    * 安全: 永远用 escapeshellarg() 处理要传递给命令的参数。

  10. 上传 + 重命名:

    • 先上传一个看似合法的文件(如 shell.php.jpg)绕过上传检查。
    • 再利用不安全的“重命名”功能将其改为 shell.php。关键在于重命名功能是否做了和上传时同样严格的后缀检查。

三、 其他潜在漏洞结合

  • 文件上传成功但无法直接执行时,寻找 LFI (本地文件包含) 漏洞来包含上传的文件(即使是 .jpg 后缀的图片马)。
  • 寻找 命令注入 (Command Injection) 漏洞,利用它来执行 mvcp 命令,将上传到非 Web 目录的文件移动到 Web 目录下,或者直接执行上传的脚本。
  • 关注 SSRF,如果上传功能支持从 URL 拉取文件,可能用于探测内网或读取本地文件。
  • 关注 XXE,如果上传的是 XML 或相关格式(DOCX, ODT)且服务器解析不当。

伪造方式

  1. 伪造文件名 (Filename Forgery):

    • 主要用来绕过基于后缀名的黑名单或白名单检查。
    • 怎么做?
      • 简单后缀修改: 服务器禁止 .php,但允许 .jpg。你在上传 shell.php 时,通过 Burp Suite 或 curl 将请求中 Content-Disposition 里的 filename="shell.php" 修改为 filename="shell.jpg"
      • 利用系统特性:
        • Windows 下末尾加点 .filename="shell.php." 可能绕过只检测 .php 的规则,但最终保存为 shell.php
        • Windows 下末尾加空格:filename="shell.php " (需在 Burp Hex 视图添加 20)。
        • NTFS 流:filename="shell.php::$DATA"
      • 大小写混淆: filename="shell.PhP" (如果服务器检查区分大小写)。
      • 双写/特殊字符: filename="shell.pphphp" (绕过简单替换),shell.php:.jpg (特定 Windows 场景)。
    • 目的: 让服务器在检查文件名/后缀时认为你上传的是一个允许的文件类型,即使实际文件内容或你想最终保存的文件名是恶意的。
  2. 伪造 Content-Type (MIME Type Forgery):

    • 有什么用? 绕过基于 HTTP 请求中 Content-Type 头部进行的服务器端验证。很多应用会检查这个值是否属于允许的 MIME 类型列表(如 image/jpegimage/png)。
    • 怎么做? 在 multipart/form-data 请求中,找到对应文件的那一部分,将其 Content-Type 头部的值修改为服务器允许的类型。例如,上传 shell.php 时,将其部分的 Content-Type 从 application/octet-stream 或 application/x-php 修改为 image/jpeg
    • 目的: 让服务器认为你上传的文件内容类型是合法的,从而通过基于 MIME 类型的检查关卡。
  3. 伪造文件内容 - 文件头 (Magic Bytes Forgery):

    • 有什么用? 绕过基于文件开头的几个“魔术字节”(Magic Bytes)进行的服务器端文件类型验证。服务器可能会读取文件的前几个字节来判断它是不是一个真正的图片、PDF 等。
    • 怎么做? 在你的 Webshell 代码(如 <?php ... ?>)前面,手动添加上目标服务器允许的文件类型的合法文件头。
      • 例如,添加 GIF89a 来伪装成 GIF 文件。
      • 添加 FF D8 FF E0 (或 FF D8 FF E1) 来伪装成 JPG 文件。
      • 添加 89 50 4E 47 0D 0A 1A 0A 来伪装成 PNG 文件。
    • 目的: 让服务器在读取文件开头进行内容校验时,认为这是一个合法的文件类型,从而通过基于文件头的检查。这通常用于制作“图片马”。
  • Title: 文件上传漏洞-学习笔记-2
  • Author: KaldX
  • Created at : 2025-04-23 20:00:00
  • Updated at : 2025-04-23 20:00:00
  • Link: https://blog.kaldx.com/2025/04/23/文件上传漏洞-学习笔记-2/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
文件上传漏洞-学习笔记-2