当前位置: 首页 > news >正文

upload-labs通关笔记-第21关 文件上传之数组绕过

目录

一、数组参数

二、PHP函数

1、empty函数

2、三元表达式

3、is_array函数

4、explode函数

5、end函数

6、reset函数

7、count函数

三、代码审计

1、MIME检查绕过

2、文件名不是数组需要设为数组

3、end函数获取文件名后缀

4、文件名处理

四、渗透实战

1、制作脚本test21.php

2、PHP版本切换

3、浏览图片

3、bp开启拦截

4、点击上传

5、bp拦截

6、修改报文

(1)修改MIME

(2)save_name文件名修改

7、发包并获取脚本地址

8、访问脚本


本文通过《upload-labs靶场通关笔记系列》来进行upload-labs靶场的渗透实战,本文讲解upload-labs靶场第21关数组绕过渗透实战。

一、数组参数

在 PHP 里,数组是一种强大且常用的数据类型数组常被用作参数传递。下面从多个方面为你详细介绍 PHP 数组参数的相关内容。cooper.php内容如下所示,那么执行效果。

<?php
$save_name = $_POST['save_name'];
var_dump($save_name);
?>

这段代码的主要功能是从 POST 请求中获取名为 save_name 的表单数据,并使用 var_dump 函数输出该数据的类型和值。 

二、PHP函数

    1、empty函数

    在 PHP 里,empty 函数用于检查一个变量是否为空。此函数可以判断多种数据类型的变量是否为空,在实际开发中,常用于验证表单数据、检查变量是否被赋值等场景。

    empty(mixed $var): bool

    (1)参数说明

    • $var:该参数是要检查的变量,它可以是任意数据类型,像字符串、整数、数组、对象等

    (2)返回值

    若变量为空,empty 函数返回 true;若变量不为空,返回 false。 

    2、三元表达式

    三元运算符的基本结构为 条件? 表达式1 : 表达式2。其工作逻辑是:先对 条件 进行判断,如果 条件 为真,则返回 表达式1 的值;如果 条件 为假,则返回 表达式2 的值。

    $file = empty($_POST['save_name'])? $_FILES['upload_file']['name'] : $_POST['save_name'];

     在这行代码里,条件是 empty($_POST['save_name'])。empty 函数用于检查一个变量是否为空。

    这段代码的含义是,如果 $_POST['save_name'] 为空,也就是用户在表单中没有填写 save_name 字段或者填写的值为空,那么 $file 会被赋值为上传文件的原始文件名;如果 $_POST['save_name'] 不为空,那么 $file 会被赋值为 $_POST['save_name'] 的值。

    3、is_array函数

    is_array是 PHP 中的一个内置函数,其主要功能是判断一个变量是否为数组类型。在编写 PHP 代码时,经常需要根据变量的类型来执行不同的操作,is_array函数可以帮助开发者快速、准确地判断变量是否为数组,从而避免因类型错误导致的程序异常。

    is_array ( mixed $value ) : bool
    • 参数 $value:该参数为必需项,代表要进行判断的变量,它可以是任意类型的数据。
    • 返回值:如果 $value 是数组类型,函数将返回布尔值 true;反之,如果 $value 不是数组类型,函数则返回布尔值 false。

    4、explode函数

    explode函数是 PHP 里一个常用的字符串处理函数,它的作用是把一个字符串依据指定的分隔符拆分成多个子字符串,最终以数组形式返回这些子字符串。

    explode ( string $separator , string $string , int $limit = PHP_INT_MAX ) : array

    (1)参数说明

    • $separator:这是必需参数,代表用于分割字符串的分隔符。它可以是单个字符,也可以是字符串。
    • $string:同样是必需参数,指的是要进行分割操作的原始字符串。
    • $limit:此为可选参数,用来指定分割后数组元素的最大数量。具体情况如下:
      • 若 $limit 为正数,返回的数组最多包含 $limit 个元素,最后一个元素会包含字符串剩余部分。
      • 若 $limit 为负数,会返回除了最后 abs($limit) 个元素之外的所有元素。
      • 若 $limit 为 0,会被当作 1 处理。

    (2)返回值

    函数会返回一个由分割后的子字符串组成的数组。

    (3)示例代码

    $file = explode('.', strtolower($file));

     假设文件名是21. php.jpg ,那么file被通过点分割分为3部分从而形成数组。 

    file[0]=21
    file[1]=php
    file[2]=jpg

    5、end函数

    end函数是一个用于操作数组的函数,它的主要作用是将数组内部指针移动到最后一个元素,并返回该元素的值。当你只需要获取数组的最后一个元素时,end 函数是一个简单直接的方法

    end(array &$array): mixed

    (1)参数说明

    • $array:这是一个必需的参数,代表要操作的数组。这里使用了引用传递(&),意味着函数会直接修改传入数组的内部指针位置。

    (2)返回值

    函数会返回数组的最后一个元素的值。如果数组为空,end 函数将返回 false

    (3)示例

    对于file数组为如下内容,end(file)将返回file的最后一个元素file[2],即jpg

    file[0]=21
    file[1]=php
    file[2]=jpg

    6、reset函数

    reset函数是用于操作数组内部指针的函数,它可以把数组的内部指针重置到第一个元素,并且返回该元素的值。

    reset(array &$array): mixed

    (1)参数说明

    • $array:这是必需参数,指的是要操作的数组。使用引用传递(&),意味着函数会直接修改传入数组的内部指针位置

    (2)返回值

    函数会返回数组的第一个元素的值。如果数组为空,reset 函数将返回 false

    (3)使用场景

    • 重新开始数组遍历:当你在遍历数组的过程中移动了指针,之后又想从数组的第一个元素开始遍历时,就可以使用 reset 函数。
    • 获取数组首元素:如果需要直接获取数组的第一个元素,reset 函数能很方便地实现

    7、count函数

    在 PHP 中,count 函数是一个常用的用于统计数组或对象中元素数量的函数。下面为你详细介绍 count 函数的相关信息。

    count(mixed $value, int $mode = COUNT_NORMAL): int

    (1)参数说明

    • $value:该参数为必需项,它可以是数组或者实现了 Countable 接口的对象。当传入其他类型的值时,若为 NULL 则返回 0;若为其他非数组和非 Countable 对象类型,返回 1。
    • $mode:此为可选参数,有两个取值:
      • COUNT_NORMAL 或 0:这是默认值,只统计数组或对象的顶层元素数量。
      • COUNT_RECURSIVE 或 1:用于递归统计多维数组中所有元素的数量,即会深入到数组的每一个子数组中进行统计。

    (2)返回值

    函数返回数组或对象中元素的数量,返回值类型为整数。

    三、代码审计

    打开靶场第21关,本关卡通过后缀过滤白名单的检测方法,具体源码如下所示。

    这段代码实现了一个简单的文件上传功能,并且对上传的文件类型进行了限制,详细版注释如下所示。

    <?php
    // 初始化上传成功标志变量,初始值为 false,表示尚未成功上传文件
    $is_upload = false;
    // 初始化消息变量,用于存储上传过程中的提示信息,初始值为 null
    $msg = null;// 检查 $_FILES 数组中名为 'upload_file' 的文件是否存在且不为空
    if (!empty($_FILES['upload_file'])) {// 定义一个数组,存储允许上传的文件 MIME 类型$allow_type = array('image/jpeg', 'image/png', 'image/gif');// 检查上传文件的 MIME 类型是否在允许的类型数组中if (!in_array($_FILES['upload_file']['type'], $allow_type)) {// 如果不在允许的类型数组中,设置提示消息为禁止上传该类型文件$msg = "禁止上传该类型文件!";} else {// 如果 MIME 类型允许,检查文件名// 如果 $_POST['save_name'] 为空,则使用上传文件的原始文件名,否则使用用户指定的保存文件名$file = empty($_POST['save_name'])? $_FILES['upload_file']['name'] : $_POST['save_name'];// 检查 $file 是否不是数组类型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)) {// 如果移动成功,设置提示消息为文件上传成功,并将上传成功标志设置为 true$msg = "文件上传成功!";$is_upload = true;} else {// 如果移动失败,设置提示消息为文件上传失败$msg = "文件上传失败!";}}}
    } else {// 如果 $_FILES['upload_file'] 为空,设置提示消息为请选择要上传的文件$msg = "请选择要上传的文件!";
    }
    ?>

    1、MIME检查绕过

    首先代码会进行MIME检查,需要修改MIME为'image/jpeg','image/png','image/gif'之一才能渗透成功,具体代码如下所示。

    $allow_type = array('image/jpeg','image/png','image/gif');
    if(!in_array($_FILES['upload_file']['type'],$allow_type)){$msg = "禁止上传该类型文件!";
    }

    2、文件名不是数组需要设为数组

    接下来获取文件名(在1.2部分讲解这部分内容,默认为$_POST['save_name']),通过判断文件名是否为数组,如果不是需要使用.分割文件名将其切割为数组(在1.4节部分讲解这部内容并进行示例)。

    $file = empty($_POST['save_name'])? $_FILES['upload_file']['name'] : $_POST['save_name'];
    if (!is_array($file)) {$file = explode('.', strtolower($file));
    }
    

    3、end函数获取文件名后缀

    这部分通过end函数获取文件后缀名(在1.5章节讲解这部分内容),换句话说这个题如果想渗透成功,确保fille的最后一个元素一定要是jpg或者png或者gif。

    $ext = end($file); // 从数组中获取最后一个元素作为扩展名
    $allow_suffix = array('jpg','png','gif'); // 类型的白名单数组
    if (!in_array($ext, $allow_suffix)) {$msg = "禁止上传该后缀文件!";
    }
    

    4、文件名处理

    $file_name = reset($file) . '.' . $file[count($file) - 1];
    • reset作用是将数组的内部指针移动到数组的第一个元素,并返回该元素的值。在explode函数处理后$file是一个数组,reset($file) 会返回数组 $file 的第一个元素,通常情况下就是文件名的主体部分(不包含扩展名)。
    • count($file) 用于返回数组 $file 中元素的数量。
    • count($file) - 1 计算得到数组 $file 最后一个元素的索引。因为在 PHP 中,数组的索引是从 0 开始的,所以最后一个元素的索引是元素数量减 1。
    • $file[count($file) - 1] 则通过这个索引获取数组 $file 的最后一个元素,通常这个元素就是文件的扩展名。
    •  .在 PHP 中是字符串连接运算符,用于将两个字符串连接成一个新的字符串。
    • reset($file) . '.' . $file[count($file) - 1]把文件名数组的第一部分、点号和文件扩展名连接起来,形成一个完整的文件名,并将其赋值给变量 $file_name。 如上代码

    如上代码用大白话来讲就是拼接文件名:数组第一个元素 + . + 数组最后一个元素

    比如文件名为cooper.php,则 $file_name = cooper + . + php

    不过这段别扭的代码暴露了一个安全问题,因为数组的元素是可以构造的,假设我们传递的数组只有2个元素,分别是第一个save_name[0]和第三个save_name[2],使得save_name[1]为空,这时最后一项是jpg使得绕过白名单,而count=2(因为没有第2个,只有1和3),举例我们使用如下方法传入参数。

    save_name[0] = cooper.php 
    save_name[2] = jpg

    使用end方法检测后缀是否白名单,于是可以绕过白名单

    end(file1)=jpg

    按照拼接方法那我们构造的文件最后的方式为:

    $file_name = cooper.php . $file[1]   ==> cooper.php.空

    这样我们的文件名拼接出来就是cooper.php.结合windows系统的尾点会被忽略,最后被存储的就是cooper.php,从而绕过服务器的检测。

    四、渗透实战

    1、制作脚本test21.php

    <?
    phpphpinfo();
    ?>
    

    2、PHP版本切换

    将php版本切换到5.3,具体如下所示。

    3、浏览图片

    进入靶场21关,选择test21.php,注意下面保存名称默认为upload-20.jpg,具体如下所示。

    3、bp开启拦截

    4、点击上传

    5、bp拦截

    bp捕获到上传报文,下图红框的部分涉及到两个文件名,其中根据源码分析我们指导save_name即为需要修改的文件名POST参数save_name的值,需要将"upload-20.jpg"后缀改为数组形式,其中save_name[0]为"upload-21.php",save_name[2]为"jpg",原始报文如下所示。

    6、修改报文

    (1)修改MIME

    Content-Type :修改为image/jpeg 或image/png 或image/gif

    (2)save_name文件名修改

    将save_name原始的"upload-29.jpg"后缀改为数组形式,其中save_name[0]为"upload-21.php",save_name[2]为"jpg",具体修改方法为复制22-25行,粘贴到26行后面

    在第一个save_name后加[0],将upload-20.jpg修改为upload-21.php
    在第二个save_name后加[2],将upload-20.jpg修改为jpg

    修改后效果如下所示。

    7、发包并获取脚本地址

    将bp的inception设置为off,此时修改后的报文发送成功。

    回到靶场的Pass21关卡,图片已经上传成功,在图片处右键复制图片地址。

     右键图片获取图片地址,如下所示获取到图片URL。 

    http://127.0.0.1/upload-labs/upload/upload-21.php.

    8、访问脚本

     如下所示访问上传脚本获取到服务器的php信息,证明文件上传成功。

    相关文章:

  • 数据结构---二叉树
  • 使用Spring Boot和Spring Security结合JWT实现安全的RESTful API
  • kafka之操作示例
  • Web安全技术体系
  • 09 接口自动化-用例管理框架pytest之allure报告定制以及数据驱动
  • 【概率论基本概念01】点估计
  • github好玩的工具
  • GitHub 趋势日报 (2025年05月23日)
  • 39-居住证管理系统(小程序)
  • 2025年第八届广西大学生程序设计大赛(热身赛)题解
  • jmeter中文乱码问题
  • x64_ubuntu22.04.5安装:cuda driver + cuda toolkit
  • JavaSE核心知识点03高级特性03-03(IO流)
  • 人工智能在医疗影像诊断上的最新成果:更精准地识别疾病
  • Oracle表索引变为不可用状态了怎么办
  • 【C/C++】红黑树学习笔记
  • python:机器学习概述
  • AI 理论- 模型优化 - 注意力机制
  • 台式机安装新的固态硬盘后无显示
  • OpenLayers 加载测量控件
  • 开发一个网站的步骤/如何在网上做销售推广
  • 中山网站制作建设/网上怎么推广产品
  • 网站建设与实现 文献综述/图片搜索
  • 海淀深圳网站建设公司/google图片搜索引擎入口
  • 做企业网站一定要企业邮箱嘛/企业培训
  • 用php做网站需要什么/有产品怎么找销售渠道