目录
UpLoad-labs靶场通关
运行环境
Pass-01
Pass-02
Pass-03
Pass-04
Pass-05
Pass-06
Pass-07
Pass-08
Pass-09
Pass-10
Pass-11
Pass-12
Pass-13
Pass-14
Pass-15
Pass-16
Pass-17
Pass-18
Pass-19
Pass-20
Pass-21
UpLoad-labs靶场通关
运行环境
windows2008 + phpstudy
靶场源码:https://github.com/c0ny1/upload-labs
Pass-01
文件上传前端验证
这里我们检查元素,发现在点击上传按钮时,通过事件(onsubmit)触发JS代码检测,在前端进行过滤检查,限制用户非法提交
分析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;
}
}
//indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。
//lastIndexOf() 方法可返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。
这里我们可以通过修改前端代码绕过检测
删除掉这个事件触发的 checkFile 函数
成功绕过,上传成功
每次上传测试成功之后,都清空一下上传文件,防止后面关卡测试时上传文件名冲突
Pass-02
文件上传MIME文件类型检测
直接分析关键代码
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
//file_exists() 函数检查文件上传目录是否存在。
if (file_exists(UPLOAD_PATH)) {
//通过$_FILES['upload_file']['type']获取并检测文件MIME类型
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']
//move_uploaded_file() 函数将上传的文件移动到新位置。
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}
$_FILES['upfile']['name']; //客户端上传文件的名称,不包含路径
$_FILES['upfile']['type']; //上传文件的MIME类型
$_FILES['upfile']['tmp_name']; //已上传文件在服务器端保存的临时文件名,包含路径
$_FILES['upfile']['error']; //上传文件出现的错误号,为一个整数
$_FILES['upfile']['size']; //已上传文件的大小,单位为字节
抓包,直接修改文件类型为容许通过的MIME类型
绕过MIME文件类型检测,成功上传
清空上传文件之后,下一关
Pass-03
后缀名黑名单限制
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
//定义黑名单
$deny_ext = array('.asp','.aspx','.php','.jsp');
//trim()去空格
$file_name = trim($_FILES['upload_file']['name']);
$file_name = deldot($file_name);//删除文件名末尾的点
//strrchr() 函数查找'.'在文件名中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符,即取文件后缀名。
$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 = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
绕过黑名单思路也很简单,可以上传.phtml .phps .php5 .pht等后缀
前提是apache的配置文件中存在如下配置
AddType application/x-httpd-php .php .phtml .phps .php5 .pht
直接上传文件后缀 .phtml即可绕过黑名单限制
测试成功
Pass-04
.htaccess绕过
限制条件
-
mod_rewrite模块开启
-
AllowOverride All
分析源代码,发现黑名单限制很死
$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")
此时,我们可以通过上传.htaccess修改当前目录配置
SetHandler application/x-httpd-php
将当前目录下的所有文件当作php来解析
上传.htaccess(注意,文件名一定是.htaccess) 配置文件到服务器upload目录下
上传成功之后,再上传任意后缀.png的webshell
成功拿到shell
Pass-05
.uert.ini绕过
查看核心代码,发现该有的防护一样都没落下
$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 . '文件夹不存在,请手工创建!';
}
}
这关是比较新的一关,跟着前人的脚步,拿下这一关
文件目录目录下的.user.ini 中的字段也会被 php 视为配置文件来处理,从而导致 php 的文件解析漏洞。
那么想要引发.user.ini解析漏洞需要三个条件
服务器脚本语言是PHP
服务器开启了CGI/FastCGI模式
上传目录下有可执行文件(才明白提示说有个readme.php)
phpstudy切换版本,nts就是Fast-CGI模式
确保环境ok之后
上传.user.ini文件,文件内容
auto_prepend_file=phpinfo.png
//auto_append_file,在php文件中自动包含指定的文件
之后上传文件phpinfo.png,文件内容就是我们的webshell
上传成功之后,我们访问readme.php就自动文件包含了webshell
Pass-06
大小写绕过
分析关键代码,发现代码基本和前面一致,但是少了strtolower()函数,将上传文件后缀转换为小写
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
//黑名单限制了htaccess
$deny_ext = array(".php",".php5",...",".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 . '文件夹不存在,请手工创建!';
}
}
上传phpinfo.PHP
执行成功
Pass-07
空格绕过
分析核心代码,发现在最后少了trim($file_ext)首尾去空格(即".php ",绕过黑名单)
$deny_ext = array(".php",".php5",...,".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);//去除字符串::$DATA
这里只需使用bp抓包 在上传文件名后加上空格,即可绕过
测试成功
Pass-08
加”.“绕过
分析核心代码
发现在开始少了没有删除文件末尾的"." deldot()
$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); //首尾去空
在后缀名中加”.”绕过
由于windows特性,会自动去掉后缀名中最后的”.”,在windwos平台上可测试成功
Pass-09
::$DATA绕过
分析核心代码
缺少了str_ireplace('::$DATA', '', $file_ext)
$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); //首尾去空
php在window的时候如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测
后缀名.且保持"::$DATA"之前的文件名
目的便是为了不检查后缀名
上传成功
去掉::$DATA后缀,访问成功
Pass-10
核心代码分析
$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); //首尾去空
bp抓包修改文件名后缀为 .php. .
访问成功
Pass-11
双写绕过
分析核心代码,发现str_ireplace()函数,自然想到双写绕过
$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","ini");
$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 = '上传出错!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
bp抓包改文件名即可
测试成功
Pass-12
00截断(GET)
需要满足条件
php版本小于5.3.4
magic_quotes_gpc = Off
分析源码,发现用户这里的save_path是 get传参,客户端本地可控,可尝试在save_path中添加%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;
//这里是%00截断的关键,save_path的参数用户可控
//原本:$img_path="../upload/754379895453.png"
//而当我们提交save_path=../upload/1.php%00
//%00截断后面的字段:$img_path="../upload/1.php"
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}
上传.png后缀的webshell
此时在upload目录下已经成功写入1.php
访问成功
Pass-13
00截断(POST)
条件跟上一关条件一样
这一关与上一关大体类似,只是save_path参数的传递形式由原来的get变为post
注意
get会自动解码 %00
post不会自动解码 %00
所以,我们在上传之前就要对%00进行解码
将解码之后的字符粘过去替换原来的%00,此时该字符不可见
成功
Pass-14
图片木马
分析核心代码
function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读2字节
fclose($file);
$strInfo = @unpack("C2chars", $bin); //解码二进制数据
//intval() 函数用于获取变量的整数值
$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 = "上传出错!";
}
}
}
选择一张图片
制作方法
直接使用记事本打开图片,将webshell加入到末尾
使用命令 copy 1.jpg /b + info.php /a 2.jpg
直接在webshell文件前面加入文件头字段,修改指定后缀
上传成功后,借用文件解析漏洞测试成功
Pass-15
getimagesize() //获取图像信息
同14关
Pass-16
exif_imagetype() // 读取一个图像的第一个字节并检查其签名
同14关
Pass-17
部分代码
//判断文件后缀和文件类型
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
//使用上传的图片生成新的图片
$im = imagecreatefromjpeg($target_path);
//imagecreatefromjpeg — 由文件或 URL 创建一个新图象
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 = "上传出错!";
}
本关综合判断了后缀名、content-type,以及利用imagecreatefromjpeg判断是否为jpeg图片,最后再做了一次二次渲染
这一关用常规的图片木马值作方法是无法绕过imagecreatefromjpeg函数的,因为imagecreatefromjpeg二次渲染它相当于是把原本属于图像数据的部分抓了出来,再用自己的API 或函数进行重新渲染,在这个过程中非图像数据的部分直接就隔离开
可以参考某大牛的绕过方式,通过比较gif图片在使用函数imagecreatefromgif转换之前和之后通过python脚本比较转换前后相似的部分,再将其替换为webshell
传送门---> https://secgeek.net/bookfresh-vulnerability/
注意上面文章末尾给出了POC.gif
需要修改php.ini的配置文件,使其支持解析<??>
上传成功测试成功
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 = '上传出错!';
}
}
可以发现文件是先上传到upload目录下,之后进行检查后缀,再重命名
但是这里有个逻辑问题,就是先上传文件,再检查后缀名,就是我们可以上传webshell.php文件到服务器端,该文件会在服务器端短暂停留一段时间,之后检测到其后缀不在白名单中,走else ,将其删除。
上传info.php文件
首先bp抓包,将其发送至intruder模块,不断重复发包
持续发包
一直刷新浏览器,终于连上了(手疼)
Pass-19
同上,条件竞争,太菜没有测试成功
Pass-20
/.绕过
核心代码分析
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
//黑名单限制
$deny_ext = array("php","php5",...,"php2",,"htaccess");
$file_name = $_POST['save_name'];
//pathinfo() 函数以数组的形式返回文件后缀
$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 . '文件夹不存在,请手工创建!';
}
}
pathinfo() 函数以数组的形式返回关于文件路径的信息
-
PATHINFO_DIRNAME - 只返回 dirname
-
PATHINFO_BASENAME - 只返回 basename
-
PATHINFO_EXTENSION - 只返回 extension
-
PATHINFO_FILENAME - 只返回 filename
这里通上传的save_name参数20.php/.此时pathinfo()取到的后缀是.php/. 绕过黑名单限制
保存时 此时的路径变为 ../upload/20.php/.
此时在upload目录下
也可通过00截断过关
Pass-21
数组+/绕过
代码审计
$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 = "请选择要上传的文件!";
}
bp抓包修改数据
测试成功
参考:
https://www.jianshu.com/p/55a50c4bf576
https://xz.aliyun.com/t/2435#toc-17
https://wooyun.js.org/drops/user.ini%E6%96%87%E4%BB%B6%E6%9E%84%E6%88%90%E7%9A%84PHP%E5%90%8E%E9%97%A8.html
https://secgeek.net/bookfresh-vulnerability/