uploads-labs-wp

3212 字
16 分钟
uploads-labs-wp

(过程详细,基本都能够参照复现) (针对 0n1y 当时感觉有趣/不会的点进行了解释) (目标是名为 phpinfo.php 的文件成功执行)

Pass-01 前端 Javascript 检测#

前端检测,直接禁用 Javascript

Pass-02 MIME 检测#

第二关直接上传得到的响应: 文件类型不正确,请重新上传!

这里抓包修改 content-typeimage/jpeg 后成功上传

Pass-03 特殊后缀解析绕过#

第三关直接上传得到的响应: 不允许上传.asp,.aspx,.php,.jsp后缀文件!

这里将后缀改为 php3 后成功上传

Pass-04 .htaccess 绕过#

第四关直接上传得到的响应: 此文件不允许上传!

这里尝试上传 .htaccess 文件后成功

.htaccess 文件内容如下: AddType application/x-httpd-php .jpeg

随后再上传 phpinfo.jpeg 成功上传

Pass-05 大小写绕过#

第五关直接上传得到的响应: 此文件类型不允许上传!

这里直接大小写绕过即可:phpinfo.phP

Pass-06 空格绕过#

第六关直接上传得到的响应: 此文件不允许上传

这里上传一个后缀末尾带空格的即可:phpinfo.php+(这里的 + 是一个空格)

Pass-07 点号绕过#

第七关直接上传得到的响应: 此文件类型不允许上传!

在文件后缀加点号进行绕过:phpinfo.php.

Pass-08 ::$DATA 绕过#

第八关直接上传得到的响应: 此文件类型不允许上传!

在后缀加上 ::$data 去进行绕过:phpinfo.php::$data

Pass-09 双点号绕过#

源码如下:

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

第九关直接上传得到的响应: 此文件类型不允许上传!

这里后端对文件名前后的空格和点号均进行了删除,但只进行了一次

这里使用双点号进行绕过:phpinfo.php. .

删除过后的文件名为:phpinfo.php. ,即可绕过

Pass-10 双写绕过#

出乎意料,这一关直接上传 phpinfo.php 竟然直接成功了

看抓包数据:

/upload/info.

这里很明显,将文件名中所有的 php 字眼的字符串全部删除了(即替换为空字符串

这里后面尝试双写绕过成功了:phpinfo.pphphp

但是其实在此之前我是先尝试的 phpinfo.phphpp,而这被后端改为 /upload/info.hpp

这里是因为后端进行匹配的是 php,而在我的字符串中 .phphpp 先出现了 php,导致前面的被删除(我的本意是中间的 php 被删除

(双写绕过也不能任意构造呀 23333

Pass-11 %00 截断绕过#

源码如下:

$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类型文件!";
}
}

第十一关直接上传得到的响应: 只允许上传.jpg|.png|.gif类型文件!

这里将文件上传路径修改为 /upload/phpinfo.php%00 去截断后续字符使得文件被系统当成 php 文件执行

Pass-12 %00 截断绕过#

源码如下:

$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类型文件!";
}
}

第十二关直接上传得到的响应: 只允许上传.jpg|.png|.gif类型文件!

初看这个响应,大概能猜到也是白名单过滤,这里看了下数据包的响应:(只截取了一部分关键的

POST /Pass-12/index.php HTTP/1.1
................................
Content-Disposition: form-data; name="save_path"
../upload/
------WebKitFormBoundarybkdk3dzqmvcVLqkl
Content-Disposition: form-data; name="upload_file"; filename="phpinfo.php"
Content-Type: application/octet-stream
<?php eval("phpinfo();"); ?>
------WebKitFormBoundarybkdk3dzqmvcVLqkl
Content-Disposition: form-data; name="submit"
上传
------WebKitFormBoundarybkdk3dzqmvcVLqkl--

这里可以看到此次上传文件是采用 POST 方法,而且也有可控的上传路径

这里同样采取 %00 截断

但是要注意的一点是 POST 方法传递的参数是不会像 GET 方式一样进行自动解码的

这里手动在 Yakit 里面对 %00 进行解码

方法:选择 %00,右键选择 URL 强制解码

最终的上传路径变为:/upload/phpinfo.php{{urldec(%00)}}

Pass13~Pass16 的目标变为 上传图片马到服务器

Pass-13 图片马配合文件包含绕过#

源码如下:

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){
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType;
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_type = getReailFileType($temp_file);
if($file_type == 'unknown'){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}

这里首先我们得知道题目目录下有这么个 php 文件: http://localhost/include.php

该内容是:

<?php
/*
本页面存在文件包含漏洞,用于测试图片马是否能正常运行!
*/
header("Content-Type:text/html;charset=utf-8");
$file = $_GET['file'];
if(isset($file)){
    include $file;
}else{    show_source(__file__);
}
?>

随后开始做题,这里我们几番尝试后发现后端会检测文件头

这里修改 phpinfo.php 文件内容为:

GIF89a
<?php eval("phpinfo();"); ?>

但是光这样还不够,若我们尝试去访问上传成功给出的目录,会发现只是一张图片

原因是这道题还要我们使用文件包含去验证图片马是否能成功执行

这里构造 payload:

http://localhost/include.php?file=./upload/9720260513162917.gif

要求做出 .gif.png.jpg 三种的图片马

这里有三种做法:

  1. 直接使用 GIF89a
  2. copy normal.jpg \a + eval.php \b webshell.jpg
  3. 使用 hex 编辑器(例如 01editor

Pass-14 图片马配合文件包含绕过#

我们这里可以使用上一关一模一样的流程进行通关

Pass-15 图片马配合文件包含绕过#

这里同样可以使用上一关一模一样的流程进行通关

Pass-16 二次渲染#

源码如下:

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])){
// 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
$filename = $_FILES['upload_file']['name'];
$filetype = $_FILES['upload_file']['type'];
$tmpname = $_FILES['upload_file']['tmp_name'];
$target_path=UPLOAD_PATH.'/'.basename($filename);
// 获得上传文件的扩展名
$fileext= substr(strrchr($filename,"."),1);
//判断文件后缀与类型,合法才进行上传操作
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);
if($im == false){
$msg = "该文件不是jpg格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".jpg";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagejpeg($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else if(($fileext == "png") && ($filetype=="image/png")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefrompng($target_path);
if($im == false){
$msg = "该文件不是png格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".png";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagepng($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else if(($fileext == "gif") && ($filetype=="image/gif")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromgif($target_path);
if($im == false){
$msg = "该文件不是gif格式的图片!";
@unlink($target_path);
}else{
//给新图片指定文件名
srand(time());
$newfilename = strval(rand()).".gif";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagegif($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}

第十六关直接上传得到的响应: 只允许上传后缀为.jpg|.png|.gif的图片文件!

这里后端将我们传入的图片进行二次渲染,将我们的恶意代码可能删去了(当然也可能我们很幸运的位置它刚好没删

这里我们要将上传前和上传后的图片进行比对

这里使用 01editor,首先我们打开这两个文件

在 Tools 选项下面找到 Compare Files,进行比对

灰色部分的就是内容一致的部分,我们将恶意代码插入到灰色区域即可

Pass-17 条件竞争#

源码如下:

$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'];
$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)){
$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 = '上传出错!';
}
}

这里使用的是 Burpsuite 的 intruder 爆破模块

题目源码是先将文件保存在服务器上,再对其进行检测并且执行删除操作

这里我们要利用它的时间间隙,在它删除成功前访问到我们上传的文件

(即使后端将文件名变为了随机数也仍然可以使用此法,就是一个快!

这里先上传文件,抓包,随后发送到 intruder

将 intruder 界面的 Payload type 改为 Null payloads

选中下方的 Continue indefinitely

随后可以跳到同样该界面的 Resource pool 界面,调快进程(可选,比如调为 20 等等

随后开始攻击

另一边,再构造一个数据包,同样发送到 intruder 模块

这一次使用 GET 方法,路径就是我们上传的 /upload/phpinfo.php,其他和上述配置一样即可

开始攻击

随后在第二个数据包的攻击界面中找响应不同的即可

Pass-18 条件竞争配合解析漏洞#

这里利用了 Apache 的一个解析漏洞:

Apache 会从右往左地解析拓展名,遇到无法识别的则往左继续走

这道题目的 .7z 是在白名单内的,但是默认状态下 Apache 是无法识别该拓展名

因此我们可以这样构建文件名 phpinfo.php.7z

其余步骤与 Pass-17 均一致,都是考察条件竞争,这里不再解释

Pass-19 大小写绕过/%00截断#

源码如下:

$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 = $_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 后缀改为了 .jpg

但是我们仔细观察抓包数据即可发现,这个重命名的操作就在我们的数据包中

也就是说,此重命名是可控的

这里我们直接修改 upload-19.jpg(这里可能会不一样)为 upload-19.PHP 即可绕过

或者我们直接构造 upload-19.php%00.jpg,也能实现 %00 截断绕过

(这里同样要对 %00 进行 URL 解码,因为是使用 POST 请求)

Pass-20 数组绕过#

源码如下:

$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
//检查MIME
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}else{
//检查文件名
$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];
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";
}
}
}
}else{
$msg = "请选择要上传的文件!";
}

这里先改 MIME 为 image/jpeg

提示说文件上传成功,保存在 "../upload/upload-20.jpg"

这里重命名操作依然可控,我们尝试上一关一样的操作

得到:提示:禁止上传该后缀文件!

此关是对我们传入的文件名进行数组校验,若不是数组,则对文件名按照 . 进行分割

若是数组,则不进行分割操作

这里进行数组校验后会分别对文件名和拓展名进行校验(分割后

这里我们可以传入数组:

save_name[0]=phpinfo.phpsave_name[2]=jpg

由于我们传入的是数组,因此不会受到分割处理

这里 end($file) 对数组最后一个元素进行校验,我们这里传入的 jpg 正在白名单内,通过校验

这里有个 reset() 函数获取我们传入数组的第一个元素,随后这个元素再和 $file[count-1] 进行拼接,这里因为我们上传的数组元素为2,因此这里的 count-1 的结果为1,于是服务器去寻找索引为1的数组元素

但是!我们上传的元素索引分别为 0 和 2,不存在索引为 1 的数组元素,因此这里会返回 NULL(即为空,因此,我们这里的 $file_name 最终的结果就是 phpinfo.php,成功上传

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

uploads-labs-wp
https://www.0n1y.org/posts/uploads-labs-wp/
作者
0n1y
发布于
2026-05-15
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
0n1y
炼就坚持仙蛊
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
7
分类
3
标签
3
总字数
17,176
运行时长
0
最后活动
0 天前

目录