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

文件上传漏洞-upload靶场5-12关

文件上传漏洞-upload靶场5-12关通关笔记(windows环境漏洞)

简介

​ 在前两篇文章中,已经说了分析上传漏的思路,在本篇文章中,将带领大家熟悉winodws系统存在的一些上传漏洞。

upload 第五关 (大小写绕过)

image-20230830130243517

思路

分析源码

$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",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".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",".htaccess");$file_name = trim($_FILES['upload_file']['name']);$file_name = deldot($file_name);//删除文件名末尾的点$file_ext = strrchr($file_name, '.');$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.'/'.date("YmdHis").rand(1000,9999).$file_ext;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = '此文件类型不允许上传!';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}

与pass-4的源码相比,此处在效验规则中,把转换小写的$file_ext = strtolower($file_ext);规则去掉了,也就是说我们上传的文件后缀,大小写将不会验证.

strtolower()函数用于将字符串中的所有大写字母转换为小写字母。这一行代码将变量$file_ext中的后缀名转换为小写字母。

例如:

  • 上传一个1.Php,给后端验证,它会直接通过验证,因为在黑名单中,没有对这个后缀进行限制,它和.php虽然是黑名单的一员,但是在计算机眼中,.Php 它不等于.php,所以才能通过验证
  • 在Windows文件系统(如NTFS)默认是不区分文件名的大小写的,只区分文件名中的字母数字字符本身。因此,无论是大写还是小写的后缀名,操作系统都会将其视为相同的文件类型。

攻击思路

image-20230831142846845

我们先上传一个webshell的php文件,使用burpsuite抓包

image-20230831143029081

在抓到的请求包重,将文件名后缀改为1.Php或1.phP(pHp已经在黑名单中,所以会出现无法上传)。

image-20230831143147949

image-20230831143240035

修改完后,放行该请求包,就发现该webshell已经被成功上传。

image-20230831143344753

webshell已经被正常解析。证明了我们已经成功了。

upload 第六关(空格绕过)

image-20230831152230486

思虑

源码分析

$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",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".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",".htaccess");$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);//去除字符串::$DATAif (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;if (move_uploaded_file($temp_file,$img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = '此文件不允许上传';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}

分析源码发现,该源码没有首尾去空格的函数( $file_ext = trim($file_ext); ),所以我们就可以利用该漏洞,上传一个后缀带有空格的webshell。

trim()函数用于删除字符串开头和结尾处的空格或其他指定字符。这一行代码将变量$file_ext中的后缀名首尾的空格去除。

原理解析

在windows中,Windows操作系统会自动删除后缀名中的空格,例如:

  • “1.php “(后缀加了空格)windows会将其更正为"1.php",以符合文件后缀名的规范。这是操作系统的默认行为,旨在确保文件的正确标识和处理。

攻击思路

和pass-05关的思路差不多,因为本关卡中设置了转化小写的操作,去掉了首位去空格的规则,我们只要稍作改变就OK。

还是规矩打开burpsuite拦截,并上传一个webshell文件。

image-20230831154325720

image-20230831154344485

修改上传webshell后缀。在.php后面加上一个空格。

image-20230831154429771

从拦截到的请求包中,找到我们上传php的路径

image-20230831154523841

把它拼接到靶场url后,进行访问

image-20230831154618916

成功运行phpinfo();函数,证明成功过关。

upload 第七关 (点号绕过)

image-20230831154836267

思路

源码分析

$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",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".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",".htaccess");$file_name = trim($_FILES['upload_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 . '文件夹不存在,请手工创建!';}
}

在本关的源码中,可以发现转换小写、首尾去空格的漏洞都已经修复,但是它缺少了 $file_ext = strrchr($file_name, ‘.’);

strrchr()函数用于从字符串中获取最后一个出现的指定字符(此处为’.‘)及其后面的所有字符。它返回从最后一个出现的指定字符到字符串末尾的子字符串。所以这一行代码的意思是将文件名中的后缀(包括’.')提取出来,并将结果赋值给变量$file_ext

原理也非常简单,这些都是windows系统对文件后缀的特性,pass5一直到pass6基本都是一个意思,在windows文件系统中:

  • 后缀名中不允许包含空格或点:
    • Windows不允许在文件后缀名中包含空格或点。如果在文件名中添加空格或点作为后缀的一部分时,系统会自动将其删除,以确保后缀的有效性。
  • 后缀名一般是不区分大小写的:
    • Windows文件系统默认情况下是不区分文件后缀名的大小写的。也就是说,.jpg 和 .JPG 会被视为同一种文件类型(JPEG图像文件)。当我们双击一个文件时,Windows会根据后缀名来确定使用哪个程序打开它,而不考虑后缀名的大小写。

攻击思路

和pass5、pass6一样的套路。打开burpsuite拦截,上传一个webshell文件。

image-20230831160436981

在拦截到的请求包中,更改webshell的后缀,在最后加上一个点.

image-20230831160554005

从响应包中,找到上传文件的路径,并拼接到靶场url中。

image-20230831160712387

成功执行,并也解析phpinfo(); 成功通关

upload 第八关(::DATA 绕过)

image-20230901014402328

思路

源码分析

$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",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".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",".htaccess");$file_name = trim($_FILES['upload_file']['name']);$file_name = deldot($file_name);//删除文件名末尾的点$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //转换为小写$file_ext = trim($file_ext); //首尾去空if (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = '此文件类型不允许上传!';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}

在这关的源码中,发现转换小写、首位去空格、删除文件末尾的点等规则操作已经全部修复。但是缺少了$file_ext = str_ireplace(‘::$DATA’, ‘’, $file_ext);的规则,他的目的去除字符串::$DATA,那么这个::$DATA是什么呢?它其实是windows系统的流文件。每个文件都可以具有一个或多个数据流,其中默认的数据流称为 “::$DATA” 流。

文件流介绍

Windows流文件是一种特殊的文件特性,允许在文件中存储额外的数据或属性。流文件是 Windows 文件系统中的一项功能,允许将文件分为多个数据流,并且每个数据流可以独立地读取和写入。

在Windows文件系统中,流文件可以根据其用途和功能可以进行分类:

  • ::$ATTRIBUTE_LIST 包含组成文件的所有属性的列表,并标识每个属性的位置。

  • ::$BITMAP 索引用于管理目录的 b 树可用空间的位图。 无论群集大小) ,b 树都以 4 KB 区块 (进行管理,这用于管理这些区块的分配。 此流类型存在于每个目录中。

  • ::$DATA 数据流。 默认数据流没有名称。 可以使用 FindFirstStreamW 和 FindNextStreamW 函数枚举数据流。

  • ::$EA 包含扩展属性数据。

  • ::$EA_INFORMATION 包含有关扩展属性的支持信息。

  • ::$FILE_NAME 文件的名称,以 Unicode 字符为单位。 这包括文件的短名称以及任何硬链接。

  • ::$INDEX_ROOT 此流表示索引的 b 树的根。 此流类型存在于每个目录中。

  • ::$OBJECT_ID 用于标识链接跟踪服务的文件的 16 字节 ID。

  • ::$REPARSE_POINT 重新分析点数据。

这些特殊的流文件在Windows文件系统的内部使用,用于存储和管理文件的元数据、属性和内容。对于普通用户和常规文件操作来说,通常不需要直接操作这些特殊流文件。这些流文件由操作系统和相关的文件系统驱动程序进行管理和处理。

流文件就像是文件夹中的小抽屉,用于存储和管理文件的额外信息,但并不影响文件的主要内容。它们可以存储标签、备注、备份、版本等等,为文件提供了更多的灵活性和功能。

假设你有一个文件夹,里面有很多文件。每个文件都是一个完整的物品,就像一个完整的文件。现在,你想在文件夹中为每个文件添加一些额外的标签或备注,以提供更多的信息。为了实现这一点,你在文件夹中为每个文件添加了一个小抽屉,称之为流文件。

这些流文件(小抽屉)不影响文件的主要内容,但它们允许你存储附加的信息。你可以将文件的标签、说明、属性等写在相应的流文件中。当你需要查看这些额外的信息时,你可以打开相应的流文件,类似于打开小抽屉查看里面的纸条。


如果我们把::$DATA 数据流文件比作文件的主要内容或实际数据。如果我们有一本书,而 ::$DATA 数据流文件就相当于书中的文字内容。

在计算机中,文件通常包含实际的数据,比如文本、图像、音频或视频等。这些数据被存储在文件的 ::$DATA 数据流文件中。它是文件系统默认的数据流,没有具体的名称。

当我们打开一个文本文件时,文本中的每个字符和单词都被存储在 ::$DATA 数据流文件中。类似地,当我们打开一个图像文件时,图像的像素信息会存储在 ::$DATA 数据流文件中。

在操作系统中,当我们访问或使用一个文件时,系统会自动处理 ::$DATA 数据流文件,以便读取和展示其中的实际数据。对于大多数用户而言,这是透明和无需关注的过程。

所以简而言之,::$DATA 数据流文件存储了文件的实际内容,就像书中的文字内容一样。它是默认的数据流,由操作系统负责处理和读取。无论是文本、图像、音频还是其他类型的文件,所有的实际数据都存储在 ::$DATA 数据流文件中。

如果我们执行1.php::$DATA 指的是读取 1.php 文件的默认数据流中的内容。操作系统会将其视为要运行 1.php 文件中的 PHP 代码,而不是直接读取默认数据流中的内容。

攻击思路

我们限制已经知道的::$DATA的作用,那么我们就可以利用这个文件流在上传webshell,并执行。

还是老规矩,打开burpsuite拦截,并上传一个webshell文件。

image-20230831165626443

::$DATA加入到1.php后面

image-20230831165730335

在返回的请求包中,找到我们刚才上传文件的路径,并拼接到靶场的url上

image-20230831165841734

image-20230831170153454

当我们直接运行这个路径时候,会报错,说我们没有访问权限。这是因为windows文件系统的特性,上传的文件 1.php::$DATA 可能只保留了原始文件 1.php,而没有创建 ::$DATA 数据流文件,所以这时我们把::$DATA去掉直接访问1.php。

image-20230831170034896

成功拿下。

upload 第九关(点空格点绕过)

image-20230901014351993

思路

源码分析

$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",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".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",".htaccess");$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 . '文件夹不存在,请手工创建!';}
}

分析该源码,之前关卡的漏洞都已经修复了,此源码的执行顺序是:

  • 第一步:去掉文件名的首尾空格
  • 第二部:删除文件名末尾的点
  • 第三步:找到文件名中的点,返回后面的扩展名
  • 第四步:把扩展名转成小写
  • 第五步:去除扩展名后字符串::$DATA
  • 第六步:去除扩展名首尾的空格
  • 第七步:判断扩展名是否在黑名单中,如果不在执行后续操作。
  • 第八步:获取临时文件路径($temp_file)。
  • 第九步:拼接目标文件路径($img_path),由上传目标文件夹和处理后的文件名组成。
  • 第十步:使用 move_uploaded_file() 函数将临时文件移动到目标位置,如果移动成功,则将 $is_upload 设置为 true

在此我们分析到,它并没有对我们的文件进行重命名的操作,这里就有很大的操作成分,我们创造一个不满足黑名单的后缀,并在执行文件验证时候,去掉多余的字符,不久🆗了,按照该源码的执行顺序 ,我们就创建一个这样的文件1.php. .

在第二步的时候会去掉1.php. .最后的.,因为我们中间有空格所以它去不掉我们第二个.,这样我们就得到了一个名为1.php. 的文件,这时候就又来疑问了。这也步是后缀.php啊,后面还有一个.呢,它能被正常解析吗?

答案是它可以被正常解析,因为windows文件系统中,文件名后缀中的点和空格字符会受到一些限制和规则。这些限制可能导致操作系统自动删除或修改文件名中的这些字符。

原理

在上传的时候它是1.php.,后端验证的时候把1.php当作了文件名,.当作了后缀名,完美的绕过了黑名单的验证,但是1.php.上传到windows服务器之后,windows服务器会自动把1.php.最后的那个.给删掉,从而我们就得到了1.php的文件

想杜绝这种漏洞的方法也非常简单,对所有上传的文件进行重命名,例如,我现在在上传一个1.php. .系统误把1.php认为是文件名了,但是我现在后端验证完了以后又要从新命名,把1.php命名为20230831,当与后缀拼接的时候,他就会变成20230831.,没有后缀了。那就自然也无法执行了。

攻击思路

说完了原理,攻击就非常简单了,还是老规矩,打开burpsuite进行拦截,并上传以个webshell

image-20230831191726188

把请求包中上传文件的名字1.php改为1.php. .

image-20230831191845453

在从响应包中,找出上传文件的路径

image-20230831191944110

测试webshell是否能成功解析phpinfo();

image-20230831192053995

成功解析,通关成功,继续下一关,

image-20230831192141962

在来看看上传后的文件,和我们理论一致,windows自动删除.,不过我们在url中加.也是可以成功解析的

upload 第十关 (双写后缀绕过)

image-20230901014341978

思路

源码分析

$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","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");$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 = '上传出错!';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}

在该源码中,我们发现了一个新函数,str_ireplace(),它的作用是在字符串中进行不区分大小写的替换操作。这个函数就是这串源码是核心

  • 函数内第一个参数是$deny_ext黑名单数组,包含要被替换的字符串
  • 函数内第二参数是空字符,它是要被替换字符串
  • 函数内第三个参数是$file_name文件名,要被搜索替换的目标字符串

这个函数的目的就在于,将文件名中的黑名单字符替换为空格字符,以进一步处理和清理文件名。

也就是说,我们上传的文件内,只要是存在黑名单中的名字,都会被替换成空,例如:

我上传一个php.jpg的图片文件经过str_ireplace()函数的清洗后,这个文件名会直接变成’.jpg,而没有文件名,因为php的文件名已经被替换成了空字符

或者我上传一个 1.php的文件,金国str_ireplace()函数清洗后,这个文件名会变成1.,扩展名被替换成了空字符

在这一串源码逻辑下,之前关卡所用的手段,均无法成功上传,因为不管你是使用pass-9中使用点空格点的漏洞,把1.php变成文件名,还是使用大小写的方法绕过黑名单,把后缀变成.Php,都无法逃脱str_ireplace()的清洗,因为它直接检查是文件名,只要文件名中存在黑名单规则的字符串,都会被它替换成空字符。

但是它也不是万能的,它也存在这一个漏洞,假如我上传的文件,用这种格式,它会怎么办呢?

1.pphphp,用刚才的规则,找出个文件名中符合黑名单的字符串,在后缀中能找出一个php,并把它替换成空字符,但是这样恰巧就帮助了我们,因为在提取出php这个字符串的时候,剩下来的文件名刚好就是1.php而且已经执行str_ireplace()函数的清洗,后面又没有其他的验证,直接就上传成功了

所以如果要使用此类的验证,一定要在加一道防线,从多处阻止黑名单数组中的成员成功上传。

攻击思路

说实话这种以上几关验证,在我们已经知道规则的情况下,可以说它们是非常lose的,有些验证甚至都不用burp suite来进行抓包分析,直接更改名称上传即可。但是作为杠精的我,就是想尝试一下,假装不知情的状态下,如何攻克这这给关卡。

老规矩,打开burp suite拦截,上传一个webs hell文件

image-20230831205759753

然后把抓到的包仍到repeater进行重发分析

image-20230831210015349

不做任何修改,直接发送给后端,然后分析后端给的响应包

image-20230831210220419

分析完响应包 ,发现一个img标签其中又以个相对地址为upload/1.。此时没有百分之百把握确定,它就是我们上传的webshell,所以使用repeater去重新发送一个请求包,并把文件名后缀改为.jpg图片格式

image-20230831210601187

重新发了请求包后,发现刚才的img图片标签中地址,也变成我们刚才更改的jpg后缀,可以确定这个就是,上传文件后返回的文件路径。

再次分析,

  • 确定了上传文件的路径为../upload/
  • 第一次我们上传``1.php,为什么上传后只剩1.`
  • 而第二次上传1.jpg却能保留后缀?
  • 后端对php后最做了限制
  • 使用后最phtml 再次进行测试

image-20230831212714023

上传1.phtml发现上传后,后缀的html没了,只留下1.p,初步怀疑后端黑名单,会针对扩展名指定删除相应的关键字,接下来上传一个1.php. .测试是否能成功上传。

image-20230831213138225

此次上传发现,1.php. .上传后,也是php关键字被删除,留下了三个.,由此确定了,后端黑根据黑名单去删除相应的关键字。继续尝试上传1.pphphp文件。

image-20230831213558959

判断正确,上传成功,尝试解析phpinfo(); 拿下权限

image-20230831213725976

成功解析phpinfo();成功过关。

到此为止,upload黑名单验证windows相关的漏洞已经全部通过,总结来说,在windows环境中,我们要不断去尝试修改后缀,去尝试判断后端验证的方式,

相对的如果我们要在windows环境中使用黑名单的方式,去防御文件上传漏洞,一定要把验证的规则写严格,使用多重验证来防御。

upload 第十一关 (%00截断 GET绕过)

image-20230901014322733

思路

源码分析

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){$ext_arr = array('jpg','png','gif');$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'];$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;if(move_uploaded_file($temp_file,$img_path)){$is_upload = true;} else {$msg = '上传出错!';}} else{$msg = "只允许上传.jpg|.png|.gif类型文件!";}
}

从源码分析,pass-11是一个白名单验证,源码大概的含义:

  1. 初始化上传状态变量 $is_upload 和消息变量 $msg

  2. 检查是否有名为 "submit" 的表单提交。

  3. 定义一个数组 $ext_arr,包含允许上传的文件扩展名(“jpg”、“png” 和 “gif”)。

  4. 使用 substr() 函数和 strrpos() 函数获取上传文件的扩展名。

    • substr($_FILES['upload_file']['name'], strrpos($_FILES['upload_file']['name'], ".")+1) 的作用是从上传文件名中获取最后一个 “.” 后面的部分,即文件扩展名。这里的 strrpos() 函数用于找到最后一个 “.” 的位置。
  5. 如果上传文件的扩展名在允许的扩展名数组 $ext_arr 中,继续处理上传流程。

    • 根据您提供的代码,如果扩展名符合允许的扩展名,代码将执行以下操作:

      • 获取上传文件的临时存储路径 $temp_file
      • 构建新的文件路径 $img_path,其中包括日期时间和随机数作为文件名的一部分。
      • 使用 move_uploaded_file() 函数将临时文件移动到目标路径 $img_path
      • 根据移动文件的结果设置 $is_upload 的值。
    • 如果扩展名不在允许的扩展名数组内,则将错误消息赋给变量 $msg,提示用户只允许上传指定类型的文件。

在此源码中出现了两个个关键函数,substr()和strrpos():

  • substr() 是 PHP 中的内置函数,用于截取字符串的一部分,它由三个参数。并有一个返回值,当截取失败时返回false

    1. 第一个参数:作用是要截取的原始字符串
    2. 第二个参数:作用是截取的起始位置。
    3. 第三个参数:它是个可选参数,作用是指定截取的长度
  • strrpos() 也是 PHP 中的内置函数,用于在字符串中查找最后一次出现指定字符或子字符串的位置,它有三个参数。它也有一个返回值如果找到了指定的字符或子字符串,则返回最后一次出现的位置(从 0 开始计数),如果没有找到指定的字符或子字符串,则返回 false

    1. 要在其中查找的原始字符串
    2. 要查找的字符或子字符串
    3. 也是可选参数,指定查找的起始位置。
  • 在本源码中substr()的两个参数表达的意义:

    • $_FILES['upload_file']['name'] 是上传文件时的文件名。$_FILES 是 PHP 中用于处理文件上传的超全局变量,upload_file 是文件上传表单中 file input 的 name 属性。
    1. strrpos($_FILES['upload_file']['name'], ".") 使用 strrpos() 函数查找文件名中最后一个 “.” 的位置。这个函数返回最后一个 “.” 的索引,即字符串中 “.” 最后出现的位置。

    2. substr($_FILES['upload_file']['name'], strrpos($_FILES['upload_file']['name'], ".") + 1) 使用 substr() 函数从文件名中截取最后一个 “.” 之后的部分,即扩展名部分。

      • strrpos($_FILES['upload_file']['name'], ".") + 1 表示截取位置的起始索引,即从最后一个 “.” 之后的下一个位置开始截取。
      • 通过将起始索引设置为 strrpos() 找到的位置加 1,可以避免将 “.” 作为扩展名的一部分。

    最终,上述代码将获取到上传文件的扩展名部分,并返回该扩展名。

    • 例如,如果上传的文件名是 “test.jpg”,则 substr() 函数将返回 “jpg”。这样就可以根据扩展名进行后续的判断和处理,验证是否在允许的扩展名列表中。

在该源码中存在最大的漏洞就是:

  • $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;

    • 使用了get方法进行传递路径拼接一个随机数和时间在加上扩展名,
    • 在使用$_GET['save_path']的时候,很容易导致路径遭到遍历攻击,从而造成文件上传到系统的任意路径上。
  • 还有一个漏洞就是move_uploaded_file,它是将上传的文件从临时目录移动到指定位置,确保上传的文件能保存到指定的目标路径,但是它在早期的php版本中存在一个漏洞。

    • 在php版本<5.3.4,并且php.ini主配置文件中的magic_quotes_gpc 关闭(off)的情况下

    • magic_quotes_gpc 是一个 PHP 配置选项,用于自动转义用户提交的 GET、POST 和 COOKIE 数据中的特殊字符。它的目的是防止数据库注入和其他类型的代码注入攻击

      magic_quotes_gpc 设置为 on 时,PHP 会自动将用户提交的数据中的特殊字符(例如单引号、双引号和反斜杠)添加反斜线进行转义。

    • 这个漏洞就是%00截断,该漏洞利用了 C 语言字符串处理函数中的空字节(null byte)终止符 \0 的特性。在一些旧版本的编程语言和应用程序中,通过将文件名或路径中的输入数据以空字节结尾,可以导致字符串处理函数错误地终止字符串,从而导致路径或文件名被截断。

      • 在计算机编程中,“%00"是一个特殊字符序列,表示ASCII码为0的字符,也称为空字符或NUL字符。它是一种控制字符,用于表示字符串的结束或标记字符串的结束位置。在某些编程语言和文件格式中,遇到”%00"会被解释为字符串的结束,后面的内容将被忽略。

这两个漏洞搭配在一起,就形成了在任意路径上,上传任意文件,使用$_GET['save_path']来控制文件路径,使用move_uploaded_file来控制文件后缀。

攻击思路

还是老规矩,开启burpsuite拦截,并把一个webshell文件的后缀,修改成能正常上传的后缀。

image-20230901014548720

在后端新建一个目录,尝试将文件上传到指定目录中,

image-20230901015111842

现在我在C盘下创建了一个test的测试目录,并在响应包里修改了想对应的路径,尝试是否能上传成功。

image-20230901015326700

这里已经成功上传到指定路径在尝试是否能解析成功。

image-20230901015817474

很遗憾,在apache的规则中:

  • Apache Web 服务器只会解析并提供位于 DocumentRoot 目录(通常是 “www” 目录)下的文件。这是为了防止恶意用户访问服务器上的敏感文件。

但是在此我们也证明了,使用$_GET[save_path]去上传文件,可能会造成,攻击者可以把恶意文件,上传到系统上的任意位置。

image-20230901020457279

使用绝对路径,也是可以的,这证明这个漏洞的可怕之处。接下来我们上传一个webshell文件到它的正常目录下,看看能否正常解析。

image-20230901020748727

还是使用了绝对路径,把webshell上传到了指定的路径下。尝试解析。

image-20230901021041442

成功解析。成功过关。

upload 第十二关 (%00截断 POST绕过)

在这里插入图片描述

思路

源码分析

$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){$ext_arr = array('jpg','png','gif');$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'];$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;if(move_uploaded_file($temp_file,$img_path)){$is_upload = true;} else {$msg = "上传失败";}} else {$msg = "只允许上传.jpg|.png|.gif类型文件!";}
}

本关卡的源码与pass-11 基本一致,唯一的区别就是,把GET方法换成的POST方法,但是它们的漏洞点和原理基本都一致的,在此也不在啰嗦了。

攻击思路

老规矩直接开burpsuite拦截,上传一个满足白名单的webshell。

image-20230901105214366

我们直接修改请求体的save_path的值,修改一个绝对路径。

image-20230901105418266

上传后直接失了,What? 什么情况,难道POST方法不支持%00截断码?我们深入去分析分析问题出现在哪里。

image-20230901105639694

在分析到发送的原始数据流中,我们发现了问题,问题就出现在我们发送的%00,它在原始数据流中被当作3个字符处理,并没有发挥截断的作用。为什么会这样呢?

原因是HTTP的报文发送和接收,一般都是使用ACLLS编码,我们在请求体中修改的%00,它其实是一种URL编码,在URL编码中%00等于ACLLS码的NUL(0x0)空字符,它在请求体中发送出去后,并不能按照我们理想的状态去解析,这里有人可能就会说,我把%去除,直接发送00不就可以了吗,答案是仍然不能被理想解析,这里我们已经发现了,它们解析规则是ACLLS码,发送0安装ACLLS码解析,它也是一个字符0x30,那我们要怎么样去触发POST方法%00截断呢?我们换个思路,如果我能去直接修改它的十六进制的数值,不就可以发送出ACLLS码中的0截断了吗,问题分析清楚后我们开始行动。

image-20230901111337352

我们在上传的文件名后,加上一个随意的字符,任何符号都可以,只要是一个字符就OK,上传这一个字符的目的,是方便我们去找到对应的位置,对该字符对应的ACLLS码进行修改。

image-20230901111608865

找到刚才我们随意输入的字符,并把它的值设置为ACLLS码中的0(NUL),因为这里是用的十六进制,十六进制的0就是

00,所以这里的值我们修改为00

image-20230901111857652

修改后,发现对应的字符变成空了,不过注意这里的空和空格是有区别的,空格的ACLLS码是0x20,而NUL(0)的ACLLS

码是0,

image-20230901112317285

image-20230901112436794

上传成功,那就证明我们的理论是正确的,接下来,再去尝试是否能正常解析webshell

image-20230901112650481

成功解析,成功完成pass-12

总结

由于Windows文件命名的特性,攻击者可能利用特殊的文件名来绕过后端验证,执行恶意操作或攻击您的应用程序。

为了最大程度地防止这些上传漏洞,我们一定要严格地、仔细地编写后端验证规则,并将其写死。

  1. 文件类型验证:除了简单地验证文件扩展名,尽可能使用多种方式来判断文件类型,如MIME类型、文件头(Magic Number)检查等。

  2. 文件名过滤:对上传的文件名进行过滤,删除或替换任何可能导致安全问题的特殊字符、空格、目录分隔符等。

  3. 文件路径控制:确保上传的文件存储在合适的位置,并阻止潜在的路径穿越攻击。不要依赖用户提供的上传文件名构造保存路径,而是自动生成文件名。

  4. 文件大小限制:限制上传文件的大小,避免过大的文件占用服务器资源或造成拒绝服务(DoS)攻击。

  5. 病毒扫描:使用病毒扫描程序对上传的文件进行检查,以确保文件不包含任何恶意代码或病毒。

  6. 文件权限:确保上传文件所在的目录具有适当的文件权限设置,限制对上传文件的访问权限。

相关文章:

文件上传漏洞-upload靶场5-12关

文件上传漏洞-upload靶场5-12关通关笔记&#xff08;windows环境漏洞&#xff09; 简介 ​ 在前两篇文章中&#xff0c;已经说了分析上传漏的思路&#xff0c;在本篇文章中&#xff0c;将带领大家熟悉winodws系统存在的一些上传漏洞。 upload 第五关 &#xff08;大小写绕过…...

Redis功能实战篇之Session共享

1.使用redis共享session来实现用户登录以及token刷新 当用户请求我们的nginx服务器&#xff0c;nginx基于七层模型走的事HTTP协议&#xff0c;可以实现基于Lua直接绕开tomcat访问redis&#xff0c;也可以作为静态资源服务器&#xff0c;轻松扛下上万并发&#xff0c; 负载均衡…...

leetcode235. 二叉搜索树的最近公共祖先(java)

二叉搜索树的最近公共祖先 题目描述递归 剪枝代码演示&#xff1a; 上期经典 题目描述 难度 - 中等 LC235 二叉搜索树的最近公共祖先 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q…...

2023物联网新动向:WEB组态除了用于数据展示,也支持搭建业务逻辑,提供与蓝图连线和NodeRed规则链类似的可视化编程能力

前言 组态编辑在工业控制、物联网场景中十分常见&#xff0c;越来越多的物联网平台也把组态作为一项标配功能。 物联网产业链自下往上由“端 - 边 - 管 - 云 -用”多个环节构成&#xff0c;组态通常是用于搭建数据展示类型的应用&#xff0c;而随着系统集成度越来越高&#x…...

react将文件转为base64进行上传

需求 将图片、pdf、word、excel等文件进行上传。图片、pdf等调接口A、word、excel等附件调接口B。接口关于文件是base64格式的参数 业务场景 上传资源&#xff0c;区分影像与附件 逻辑思路 使用原生input标签&#xff0c;typefile&#xff0c;进行上传上传后的回调&#x…...

生成式人工智能能否使数字孪生在能源和公用事业行业成为现实?

推荐&#xff1a;使用 NSDT场景编辑器 快速搭建3D应用场景 克服障碍&#xff0c;优化数字孪生优势 要实现数字孪生的优势&#xff0c;您需要数据和逻辑集成层以及基于角色的演示。如图 1 所示&#xff0c;在任何资产密集型行业&#xff08;如能源和公用事业&#xff09;中&…...

SpringBoot集成JWT token实现权限验证

JWTJSON Web Token 1. JWT的组成 JWTHeader,Payload,Signature>abc.def.xyz 地址&#xff1a;JSON Web Tokens - jwt.er 1.1 Header Header:标头。 两个组成部分&#xff1a;令牌的类型&#xff08;JWT&#xff09;和所使用的签名算法&#xff0c;经过Base64 Url编码后形成…...

算法通关村第11关【青铜】| 位运算基础

1.数字在计算机中的表示 原码、反码和补码都是计算机中用于表示有符号整数的方式。它们的使用旨在解决计算机硬件中的溢出和算术运算问题。 原码&#xff08;Sign-Magnitude&#xff09;&#xff1a; 原码最简单&#xff0c;它的表示方式是用最高位表示符号位&#xff0c;0表示…...

无涯教程-Android - RadioGroup函数

RadioGroup类用于单选按钮集。 如果我们选中属于某个单选按钮组的一个单选按钮,它将自动取消选中同一组中以前选中的任何单选按钮。 RadioGroup属性 以下是与RadioGroup控制相关的重要属性。您可以查看Android官方文档以获取属性的完整列表以及可以在运行时更改这些属性的相关…...

降噪音频转录 Krisp: v1.40.7 Crack

主打人工智能降噪服务的初创公司「Krisp」近期宣布推出音频转录功能&#xff0c;能对电话和视频会议进行实时设备转录。该软件还整合的ChatGPT&#xff0c;以便快速总结内容&#xff0c;开放测试版于今天上线。 随着线上会议越来越频繁&#xff0c;会议转录已成为团队工作的重…...

基于React实现:弹窗组件与Promise的有机结合

背景 弹窗在现代应用中是最为常见的一种展示信息的形式&#xff0c;二次确认弹窗是其中最为经典的一种。当我们在React&#xff0c;Vue这种数据驱动视图的前端框架中渲染弹窗基本是固定的使用形式。 使用方式&#xff1a;创建新的弹窗组件&#xff0c;在需要弹窗的地方引用并…...

docker使用(一)生成,启动,更新(容器暂停,删除,再生成)

docker使用&#xff08;一&#xff09; 编写一个 Dockerfile构建镜像构建失败构建成功 运行镜像运行成功 修改代码后再次构建请不要直接进行构建&#xff0c;要将原有的旧容器删除或暂停停止成功删除成功再次构建且构建成功&#xff01; 要创建一个镜像&#xff0c;你可以按照以…...

用Qt自制一个小闹钟

小闹钟 功能 当按下启动按钮时&#xff0c;停止按钮可用&#xff0c;启动按钮不可用&#xff0c;闹钟无法设置&#xff0c;无法输入自定义内容 当按下停止按钮时&#xff0c;暂停播报&#xff0c;启动按钮可用&#xff0c;闹钟可以设置&#xff0c;可以输入自定义内容 .pro文…...

Vue2.0/Vue3.0使用xlsx+xlsx-style实现导出Excel文件

一、依赖导入 1、Vue2 Webpack构建的 npm i xlsx npm i xlsx-style npm i file-saver同时修改以下&#xff1a; 解决 Can’t resolve ‘./cptable’ in ‘…’ 的问题&#xff0c;在 vue.config.js 文件中加入该配置 module.exports {externals: {./cptable: var cptable}…...

【Kafka系列】(一)Kafka入门

有的时候博客内容会有变动&#xff0c;首发博客是最新的&#xff0c;其他博客地址可能会未同步,认准https://blog.zysicyj.top 首发博客地址 系列文章地址 Kafka是什么&#xff1f; 一句话概括&#xff1a;「Apache Kafka 是一款开源的消息引擎系统」 什么是消息引擎系统&#…...

外包干了2个月,技术退步明显了...

先说一下自己的情况&#xff0c;大专生&#xff0c;19年通过校招进入湖南某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年8月份&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…...

python实现语音识别

1. 首先安装依赖库 pip install playsound # 该库用于播放音频文件 pip install speech_recognition # 该库用于语音识别 pip install PocketSphinx # 语音识别模块中只有sphinx支持离线的&#xff0c;使用该模块需单独安装 pip install pyttsx3 # 该库用于将文本转换为语音播…...

java八股文面试[多线程]——线程的状态

5种状态一般是针对传统的线程状态来说&#xff08;操作系统层面&#xff09; 6种状态&#xff1a;Java中给线程准备的 NEW&#xff1a;Thread对象被创建出来了&#xff0c;但是还没有执行start方法。 RUNNABLE&#xff1a;Thread对象调用了start方法&#xff0c;就为RUNNABLE状…...

Go学习[合集]

文章目录 Go学习-Day1Go学习-Day2标识符变量基础语法字符串类型类型转换string和其他基本类型转换其他类型转stringstring转其他类型 指针类型运算符标准IO分支语句 Go学习-Day3循环语句函数声明init函数匿名函数闭包defer Go学习-Day4函数值传递&#xff0c;引用传递常用的函数…...

代码随想录算法训练营第42天 | ● 01背包问题,你该了解这些! ● 01背包问题,你该了解这些! 滚动数组 ● 416. 分割等和子集

文章目录 前言一、01背包问题&#xff0c;你该了解这些&#xff01;二、01背包问题&#xff0c;你该了解这些&#xff01; 滚动数组三、416. 分割等和子集总结 前言 01背包 一、01背包问题&#xff0c;你该了解这些&#xff01; 确定dp数组以及下标的含义 对于背包问题&#x…...

解决DNS服务器未响应错误的方法

​当你将设备连接到家庭网络或具有互联网接入功能的Wi-Fi热点时,由于各种原因,互联网连接可能无法正常工作。本文中的说明适用于Windows 10、Windows 8和Windows 7。 无法连接到DNS服务器的原因 故障的一类与域名系统有关,域名系统是世界各地互联网提供商使用的分布式名称…...

SpringBoot的HandlerInterceptor拦截器使用方法

一、创建拦截器 通过实现HandlerInterceptor接口创建自己要使用的拦截器 import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.…...

java 常用 jar 包

1 Lombok 是一个 java 类库&#xff0c;它利用注解方式自动生成 java bean 中 getter、setter、equals 等方法&#xff0c;还能自动生成 logger、toString、hashCode、builder 等 日志相关变量、Object 类方法或设计模式相关的方法&#xff0c;能够让你的 代码更简洁&#xff0…...

C#面试十问

1&#xff1a;C#中变量类型分为哪两种&#xff1f;它们的区别是什么&#xff1f;2&#xff1a;Class和Struct的区别&#xff1f;3&#xff1a;C#中类的修饰符和类成员的修饰符有哪些&#xff1f;4&#xff1a;面向对象的三个特征&#xff08;特点&#xff09;是什么&#xff1f…...

Day 41 动态规划part03 : 343. 整数拆分 96.不同的二叉搜索树

96. 不同的二叉搜索树 给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a; 输入&#xff1a;n 1…...

四轴飞行器的电池研究(MatlabSimulink仿真)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

准备HarmonyOS开发环境

引言 在开始 HarmonyOS 开发之前&#xff0c;需要准备好开发环境。本章将详细指导你如何安装 HarmonyOS SDK、配置开发环境、创建 HarmonyOS 项目。 目录 安装 HarmonyOS SDK 配置开发环境 创建 HarmonyOS 项目 总结 1. 安装 HarmonyOS SDK HarmonyOS SDK 是开发 Harmo…...

Java 面试 - Redis

Redis Redis 是基于键值对的非关系型数据库。Redis 拥有string、hash、list、set、zset等多种数据结构, redis具有惊人的读写性能, 其优秀的持久化机制是的它在断电和机械故障时也不会发生数据丢失, 可以用于热点数据存放, 还提供了键过期、发布订阅、食物、流水线、LUA脚本等多…...

【Go 基础篇】Go语言结构体之间的转换与映射

在Go语言中&#xff0c;结构体是一种强大的数据类型&#xff0c;用于定义和组织不同类型的数据字段。当我们处理复杂的数据逻辑时&#xff0c;常常需要在不同的结构体之间进行转换和映射&#xff0c;以便实现数据的转移和处理。本文将深入探讨Go语言中结构体之间的转换和映射技…...

Java 多线程系列Ⅳ(单例模式+阻塞式队列+定时器+线程池)

多线程案例 一、设计模式&#xff08;单例模式工厂模式&#xff09;1、单例模式2、工厂模式 二、阻塞式队列1、生产者消费者模型2、阻塞对列在生产者消费者之间的作用3、用标准库阻塞队列实现生产者消费者模型4、模拟实现阻塞队列 三、定时器1、标准库中的定时器2、模拟实现定时…...

江苏省建筑网站/网站友情链接有什么用

1、MyBatis是什么&#xff1f; MyBatis源自于IBatis&#xff0c;是一个持久层框架&#xff0c;封装了jdbc操作数据库的过程&#xff0c;使得开发者只用关心sql语句&#xff0c;无需关心驱动加载、连接&#xff0c;创建statement&#xff0c;手动设置参数&#xff0c;结果集检索…...

seo服务指什么意思/太原百度快速优化排名

一、C 变量类型 变量其实只不过是程序可操作的存储区的名称。C 中每个变量都有指定的类型&#xff0c;类型决定了变量存储的大小和布局&#xff0c;该范围内的值都可以存储在内存中&#xff0c;运算符可应用于变量上。 变量的名称可以由字母、数字和下划线字符组成。它必须以…...

进一步优化供给推动消费平稳增长/青岛 google seo

前言 大厂面试一直都是程序员圈内摸鱼时间津津乐道的话题&#xff0c;进大厂想必也是无数程序员的梦想。 关于“原理”的问题&#xff0c;几乎是现如今Android开发岗必问的问题&#xff0c;尤其在大厂面试中更为突出。有过大厂面试经验的小伙伴应该知道&#xff1a;大厂的面试…...

学校网站建设栏目设置/广告投放代理商加盟

Swift JSON 发展史 最开始的时候还是使用NSJSONSerialization转成字典和数组来使用&#xff01;后来苹果用Swift重新实现了JSONSerialization可以避免用NSArray和NSDictionary来桥接&#xff0c;提高解析效率。随后很多三方JSON库相继出现&#xff0c;例如&#xff1a;SwiftyJS…...

济南专业网站托管公司/百度手机版网页

例如&#xff1a;多个占位符格式化字符串&#xff1a;People {"name": "john", "age": 33}print("My name is {name},iam{age} old".format_map(People))1、定义和用法类似 str.format(*args, **kwargs) &#xff0c;不同的是 mappin…...

中心网站建设/青岛神马排名优化

LeetCode1.两数之和JavaScript 给定一个整数数组和一个目标值&#xff0c;找出数组中和为目标值的两个数。你可以假设每个输入只对应一种答案&#xff0c;且同样的元素不能被重复利用。实例&#xff1a; 给定 nums [2, 7, 11, 15], target 9因为 nums[0] nums[1] 2 7 9所…...