靶场环境
下载链接:https://codeload.github.com/c0ny1/upload-labs/zip/refs/heads/master
使用小皮集成环境来完成这个靶场 将文件放到WWW目录下就可以进行访问
进入关卡后页面呈现:
Pass-01(前端绕过)
我们先尝试上传一个web.php的文件
这里提示我们只允许上传这三种文件后缀名的文件 我们查看一下源码
发现上传文件中定义了一个checkFile()函数 我们来看一下chekFile()是什么
我们可以用砍出来这是定义了一个白名单 是由前端JS来检测 其中file.lastIndexOf(".")是截取文件最后一个点(.)之后的后缀 也就是截取文件的后缀名来判断是不是在白名单内
既然它是通过JS来检测的那我们可以禁止浏览器运行JS代码来绕过这个检测
刷新页面后再次上传我们的web.php文件
上传成功
Pass-02 (文件类型检测)
我们尝试上传一个web.php文件 显示文件类型不正确
查看一下网页源代码发现一样有checkFile()函数但是在前端页面中没有找到checkfile规则 可能没有在前端进行过滤 大概率是后端验证 再往下看我们能发现 下面有检测文件类型的代码
我们抓包看一下
这个时候我们只需要把类型改成它允许的类型send过去就完成了
Pass-03(php3绕过)
这明显是个黑名单 我们查看源码
分析代码
$is_upload = false; 和 $msg = null; 初始化了两个变量,其中 $is_upload 用于标记文件是否成功上传,$msg 用于存储可能的错误消息或状态信息。通过 isset($_POST['submit']) 来检查是否有表单提交。然后它定义了一个数组 $deny_ext,包含了不允许上传的文件后缀, 再用deldot()函数去除文件名末尾的点。然后判断如果上传的文件后缀名不在黑名单内则先获取上传文件的临时文件名然后构建新的文件路径再将临时文件移动到指定位置。如果移动成功,$is_upload则将设置为ture,否则将错误信息赋给$msg。
在我们查看Apache中的配置文件我们可以知道
AddType application/x-httpd-php .php .phtml .php3 是指将.php .php3 .phtml的文件当作php文件来执行 这时我们发现.php3不在黑名单内 我们尝试修改文件名再上传
上传成功
Pass-04 (.htaccss重写绕过)
本关还是一个黑名单限制 我们查看一下源代码
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".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");$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.'/'.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 . '文件夹不存在,请手工创建!';}
}
与第三关代码逻辑相同
.htaccess
htaccess可以帮我们实现包括:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。
这个时候我们只需要创建一个.htaccess文件和我们的web.php在同一个目录下 htaccess文件内输入
Sethandler application/x-httpd-php
然后先上传.htaccess文件再更改我们恶意代码的后缀名 上传web.jpg文件即可将我们的php文件上传成功
Pass-05 (大写绕过)
这一关将.htaccess的文件也拉进黑名单了
查看源码
$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 . '文件夹不存在,请手工创建!';}
}
这一关我们发现居然没有转小写(PHP不区分大小写) 那我们直接把文件后缀改成.PHP就好了
Pass-06 (Windows特性绕过——空格绕过)
这一关提示没有限制.htaccess文件 尝试上传后还是提示此文件不允许上传 我们查看源码
分析代码
$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 . '文件夹不存在,请手工创建!';}
}
(我们发现代码中是有过滤.htaccess文件的但上面没显示)
这一关代码和前面的关卡比 没有首位去空了 那我们就可以通过在文件后缀名上加空格来绕过
这里涉及到Windows的特性 它会自动把文件后缀后面的空格删除掉 这样对于我们来说 有空格可以绕过它的黑名单 但对于Windows来说 空格是不存在的 依然可以解析
Pass-07(Windows特性绕过——加`.`绕过)
分析代码
$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 . '文件夹不存在,请手工创建!';}
}
这一次他没有去除掉末尾的点 那我们就可以在文件后缀名加个点来绕过 Windows的特性会自动删除后面的空格和点以及::$DATA(非法字符) 所以上传的文件还是可以解析的
Pass-08(Windows特性绕过——::&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 = 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 . '文件夹不存在,请手工创建!';}
与第七关同理 这一关没有过滤::$DATA
Pass-09 (Windows特性绕过——综合)
分析源码
$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全都过滤了 但我们发现只是末尾的点 那我们可以通过输入一个点一个空格再加一个点的方式来绕过
Pass-10 (双写绕过)
这一关会把这些后缀名全部删掉
分析源码
$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; //有个upload文件夹 if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}
双写问题
那试一下双写php 发现可以上传 且可以解析 只过滤了第一个php 自然而然p和后面的hp又组成了php
Pass-11 (截断漏洞——%00截断GET上传)
分析源码
$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;
//定义了一个save_path 然后把临时文件进行了一次重命名if(move_uploaded_file($temp_file,$img_path)){ //将上传的文件复制到img_path$is_upload = true;} else {$msg = '上传出错!';}} else{$msg = "只允许上传.jpg|.png|.gif类型文件!";}
}
注:此截断漏洞只能在php5.3.x以下的版本才有 因为php底层还是C语言 C语言的截断就是%0
明白代码后我们可以可以进行如下操作 在upload下写一个php 用%00去截断后面的文件名 然后将我们的web.php文件改成.jpg文件 这样绕过上传成功后我们的web.jpg文件的内容会复制给lizhi.php里面去作为php文件执行
Pass-12 (截断漏洞——%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类型文件!";}
}
这一关我们发现传参方式变成POST了 与上一关同理我们可以通过抓包修改参数 用%00来截断文件名
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的一种机制 一共有四个函数 include、require、include_once、require_once 它们的特性——包含的所有文件 都被当成php文件来执行
我们找到这个include知道他GET传的文件名是file之后 我们找一个.jpg文件 再自己写一个php文件 将两个文件融合
这个时候我检查一下我们融合的jpg文件 php代码有没有融合进去
OK这个时候我们把这个文件上传进去 还没结束 我们上传的依旧是一个.jpg文件 没法执行php代码 这个时候我们只需要复制图片地址然后在url里面粘贴赋值给file即可 其他格式同理
Pass-14 (图片检测)
查看源码
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;}
}$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){$temp_file = $_FILES['upload_file']['tmp_name'];$res = isImage($temp_file);if(!$res){$msg = "文件未知,上传失败!";}else{$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;if(move_uploaded_file($temp_file,$img_path)){$is_upload = true;} else {$msg = "上传出错!";}}
}
和13关相同的技巧可以解决
Pass-15 (图片检测)
function isImage($filename){//需要开启php_exif模块$image_type = exif_imagetype($filename); //检测格式switch ($image_type) {case IMAGETYPE_GIF:return "gif";break;case IMAGETYPE_JPEG:return "jpg";break;case IMAGETYPE_PNG:return "png";break; default:return false;break;}
}
13、14、15关同理
Pass-16 (图片渲染绕过)
这一关我们再尝试上传我们的带有php代码的图片的时候发现 图片虽然上传成功了但是我们发现 我们写的php代码不存在了 消失了 这个时候我们只能去看一下源码
分析源码
$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()这个函数它是把这些数据块全都打散了 还是只打散了一部分 如果只打散了一部分 那我们把一句话木马插入到它没打散的部分 那就行的通了
既然我们有思路了 把上传的图片保存到本地 然后用beyond compare来对比原图和渲染之后的图 对比查看哪里没有被渲染 找到没有被渲染的地方加入我们的php代码 修改后再重新上传
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 = '上传出错!';}
}
注:访问php文件的时候 php文件可以自动生成其他的php文件 我们就可以利用这个特性将新生成的php文件生成到上一级目录
我们注意到是先上传再删除的 那我们就可以在他删除前的那短时间内将我们的php文件进行操作 让我们的php文件在它的上一级目录生成一个新的一句话木马
我们写一个恶意代码 在我们访问成功的时候在他上级目录生成我们新的恶意代码
<?php file_put_contents('../web.php','<?php phpinfo(); ?>');?>
然后打开我们的burpsuite暴力破解一下
然后我们抓住他删除前的时间内执行我们的php文件就可以将一句话木马放到它的上级目录