upload-labs 文件上传靶场

目录

前言

Pass-1

Pass-2

Pass-3

Pass-4

Pass-5

Pass-6

Pass-7&8

Pass-9

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是一个使用php语言编写的,专门收集渗透测试和CTF中遇到的各种上传漏洞的靶场。旨在帮助大家对上传漏洞有一个全面的了解。目前一共21关,每一关都包含着不同上传方式。

(图来自:  先知社区)

Pass-1

直接上传php会被判断处于黑名单中

观察源码可知, 是前端js判断类型:

方法1

直接改前端代码:

上传成功之后并没有显示文件的路径, 那么好办, 直接dir爆破:

扫到上传目录是uplod, 然后直接链接webshell即可:

http://192.168.10.100:14722/upload/door.php

方法2

抓包修改文件后缀, 先把webshell脚本改为png图片后缀, 然后抓包重新修改为php后缀:

Pass-2

可以看到, 这关虽然和上一关相似, 看似是前端js过滤的类型, 实则过滤代码部分在服务端处理:

方法1

通过修改MIME类型绕过服务端的黑名单过滤:

 

Pass-3

用上一关MIME类型的方法尝试无果, 初步判断是根据文件后缀进行黑名单过滤了

方法1

可以用截断的方法绕过:

方法2

  • phtml & php3

PHTML(有时叫做PHP)网页是一种包含PHP(一种和JavaScript或 Microsoft VBScript类似的语言)脚本的网页和ASP一样,PHP脚本镶嵌在网页的HTML代码之中。在页面被发送给请求的用户之前,网页服务器调用PHP解释程序来解释和执行PHP脚本。含有PHP脚本的网页通常都以“.php”、“.php3”或“.phtml”作为后缀。和ASP一样,PHP可以被认为是一种“动态网页”。

由于服务端采用黑名单的过滤方式, 这里可以使用php3或者phtml的后缀上传webshell:

上传成功!

Pass-4

这关也是采用黑名单过滤, 几乎过滤了所有有问题的后缀:

但是唯独没有发现对.htaccess文件的过滤, 于是编写一个.htaccess:

<FilesMatch "hack">
	# 修改文件类型 – 下面可以让任何的文件都成为PHP那么被服务器解释
	SetHandler application/x-httpd-php
	AddType application/x-httpd-php .jpg
</FilesMatch>

然后将.htaccess上传到服务器, 在上传包含webshell的图片格式脚本:

包含hack字段的文件名即可被当做php解析:

为什么要匹配特定字段的文件名呢?

一部分原因是因为要隐藏这个webshell不让用户发现异常。

Pass-5

禁止了.htaccess文件的上传, 同时也无法用大小写混写绕过

方法1

由于服务器的中间件是apache2, 尝试解析漏洞:

然后访问, 发现解析成功了, apache2解析漏洞 (存在于Apache1.x和Apache2.x)

将从右向左解析后缀, 直到遇到一个apache认识的后缀类型位置:

方法2

查看提示:

于是想到.user.ini

  • .user.ini

在php执行的过程中,除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER[‘DOCUMENT_ROOT’] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。 .user.ini 中可以定义除了PHP_INI_SYSTEM以外的模式的选项,故可以使用 .user.ini 加上非php后缀的文件构造一个shell,比如 auto_prepend_file=01.gif 

具体请参考:  https://segmentfault.com/a/1190000011552335?utm_source=tag-newest

创建一个 .user.ini 文件, 内容如下:

auto_prepend_file=123.gif

将其上传到服务器,  然后将包含webshell的脚本命名为 123.gif, 同样也上传服务器

再访问readme.php的时候, 就会自动包含并解析123.gif文件

Pass-6

源码中没有很好地过滤大小写, 采用大小写混写绕过:

Pass-7&8

禁用了上传ini文件类型,  尝试其他的截断上传, 解析上传也无果

方法1

这里就可以利用系统命名漏洞, 且服务端没有进行相应的过滤

在Windows系统中,上传 index.php. 会重命名为 . ,可以绕过后缀检查。 也可尝试 index.php%20 , index.php:1.jpg index.php::$DATA 等。 在Linux系统中,可以尝试上传名为 index.php/. 或 ./aa/../index.php/. 的文件

方法2

没有对后缀后面的空格做处理, 加一个空格即可绕过:

Pass-9

fuzz了所有可解析的后缀, 都无法上传,  查看源码发现对所有可解析的后缀做了过滤:

但并未对::$DATA做过滤

利用Windows下NTFS文件系统的一个特性,即NTFS文件系统的存储数据流的一个属性 DATA ,就是请求 a.asp 本身的数据,如果a.asp 还包含了其他的数据流,比如 a.asp:b.asp,请求 a.asp:b.asp::$DATA,则是请求a.asp中的流数据b.asp的流数据的内容。

再访问 http://localhost:14722/upload/hack.php 即可。

Pass-10

查看源码,  观察代码逻辑, 可以看到, 

先对后缀的最会一个 .(点)进行了删除,

然后再截取了最后一个 .(点) 到字符串结尾的部分, 

最后判断截取部分是否在黑名单内。

但是在拼接文件名的时候, 使用的是删除 .(点)后的内容, 出现处理逻辑错误

构造上传文件名:  hack.php. .

上传处理之后:     hack.php.

继续用Pass-07的做法,  利用Windows系统命名来绕过检测, 上传 index.php. 会自动重命名为 index.php ,可以绕过后缀检查。

Pass-11

测试上传一个php脚本,  发现php后缀被替换为空了:

用双写绕过即可:

再访问脚本:

Pass-12

上传一个door.asd 来判断是黑名单还是白名单:

可见是白名单过滤

继续审计源码,  可以看到GET存在一个参数, 体现在url中; 然后将该参数的路径值(要上传到制定目录的位置)和上传的文件名后缀做拼接, 得到一个全新的上传文件:

1. 在GET参数save_path中传入真正的webshell名,  然后在后面加一个截断符%00

2. 然后在POST的filename为正常白名单的文件名

那么可以采用截断绕过

  • %00截断适用条件

PHP 版本 < 5.3.4

php.ini 中 magic_quotes_gpc=off

通过服务端的白名单过滤和拼接之后,  save_path内容后面的值就被截断了, 达到了绕过的效果:

Pass-13

与上一关大同小异, 只不过save_path的参数由GET型改为了POST型:

绕过方法与上一关换汤不换药。

Pass-14

有的站点使用文件头来检测文件类型,这种检查可以在Shell前加入对应的字节以绕过检查。几种常见的文件类型的头字节如下表所示

类型 二进制值
JPG FF D8 FF E0 00 10 4A 46 49 46
GIF 47 49 46 38 39 61
PNG 89 50 4E 47
TIF 49 49 2A 00
BMP 42 4D

观察源码, 服务端读取了上传文件的前2个字节进行内容的判断, 然后得以判断文件是否处于白名单的类型:

鉴此, 就可以利用向shell图片文件里面添加jpg格式的类型来达到一个免杀的效果,  再加上文件包含漏洞来执行包含shell的图片, 完成攻击:

在shell里添加jpg的二进制头子节:

修改为jpg头子节的10个16进制:

成功绕过:

当然也可以改其他图片类型的16进制, 可自己尝试。

Pass-15

这关利用了getimagesize() 函数来判断文件的类型:

  • getimagesize() 函数

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

getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型及图片高度与宽度。

绕过方法和Pass-14一样

Pass-16

同样, 这关使用 exif_imagetype()函数来判断文件类型 (需要开启php_exif模块), 

  • exif_imagetype() 函数

判断一个图像的类型

exif_imagetype() 读取一个图像的第一个字节并检查其签名。

绕过方法同上。

Pass-17

审计源码:

部分函数:

  • imagecreatefromjpeg()函数

由文件或 URL 创建一个新图象。返回一图像标识符,代表了从给定的文件名取得的图像。

  • basename() 函数

读取路径中的文件名部分

  • imagejpeg() 函数

使用图片对象生成图片文件

首先检测文件是否是jpg, png, gif格式:

if(($fileext == "jpg") && ($filetype=="image/jpeg"))

使用move_uploaded_file函数来做判断条件,如果成功将文件移动到$target_path, 就会进入二次渲染的代码, 反之上传失败

if(move_uploaded_file($tmpname,$target_path))

在这里有一个问题:  在move_uploaded_file()返回true的时候, 就已经成功将图片马上传到服务器了, 所以下面的二次渲染并不会影响到图片马的上传,:

那么二次渲染的代码就很多余, 心里知道就好,  主要考察二次渲染, 那么我这里把move_uploaded_file() 这个函数去掉, 直接应用二次渲染的代码:

由于环境问题, 一下解析参考于:  https://xz.aliyun.com/t/2657#toc-1

然后将<?php phpinfo(); ?>添加到door.gif的尾部,  然后上传之后, 

成功上传含有一句话的door.gif,但是这并没有成功.我们将上传的图片下载到本地.

可以看到下载下来的文件名已经变化,所以这是经过二次渲染的图片.我们使用16进制编辑器将其打开.

可以发现,我们在gif末端添加的php代码已经被去除.

关于绕过gif的二次渲染,我们只需要找到渲染前后没有变化的位置,然后将php代码写进去,就可以成功上传带有php代码的图片了.

经过对比,蓝色部分是没有发生变化的:

我们将代码写到该位置.

上传后在下载到本地使用16进制编辑器打开

可以看到php代码没有被去除.成功上传图片马。

Pass-18

  • 竞争上传

有的服务器采用了先保存,再删除不合法文件的方式,在这种服务器中,可以反复上传一个会生成Web Shell的文件并尝试访问,多次之后即可获得Shell。

审计源码:

基本确定是白名单过滤, 

过程是先将上传的临时文件移动到目标路径(upload下);

然后进行白名单判断,  是则以时间戳重命名目标文件; 否则就断开目标文件的软连接(可以理解为删除)。

这里就不适合用截断上传了,  可用竞争上传

即进行若干次重复的上传脚本文件,  同时不断访问目标文件,  总有一次会趁系统来不及unlink文件而访问到上传的脚本文件, 称之为竞争上传。

Intruder模块进行无数次上传操作:

同时多个线程进行:

同时不断访问上传之后的脚本:  http://192.168.1.10/upload/door.php 

总可能访问到系统来不及unlink的文件 (虽然我没有成功 = =)

Pass-19

审计代码:

 myupload.php :

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

......
......
......  
  /** 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" );
  
  }
......
......
...... 
};

白名单过滤+if流水式判断过滤, 

绕过依旧可以采用竞争上传。

Pass-20

上传之后用户可以自定义文件名,  是一个很大的威胁:

抓包可以看到,  用户自定义的文件名是通过POST方式发送给服务器的:

且最终上传的文件名是该save_name参数的值,  所以绕过它即可

判断得知是黑名单过滤:

方法1

大小写绕过

由于是黑名单过滤,  黑名单往往会不全,  所以绕过也是得心应手的容易:

访问:

方法2

利用系统命名

Windows下命名为 door.php. ,  系统会自动命名为 door.php ; 

Linux下命名为 door.php/. ,  系统会自动命名为 door.php ; 

Pass-21

题来自CTF

同样是用户可自定义上传的文件名

审计代码:

$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']; // 上传文件名和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文件类型;

然后将上传的文件名 (或者用户自定义的文件名) 转换为小写, 再以 '.' 分割;

再取后缀进行白名单判断;

通过白名单后, 进行新文件名的拼接 (关键);

最后移动文件完成上传。

关键就处在拼接新文件的地方:

 $file_name = reset($file) . '.' . $file[count($file) - 1]; // 取文件名和后缀进行拼接

这段代码出现在白名单通过之后可控,  所以只要构造好 $file 就可以绕过。

如何构造呢?

假如可以在POST中传递我们自定义好的 $save_name 数据,  那不是就完成了构造?

举个例子,  看下图:

$a[1] 没有设置,  所以为空, 这样count($a)的数量就是2 (已设置的数量)

当取  $a[count($a) - 1];  的时候,  就相当于取到 $a[1],  其值为NULL

联想到以上代码, 

若构造 $file数组的值为:   

$file[0] = upload-21.php

$file[2] = png

通过白名单后的拼接就是:  upload-21.php.

这样POST到服务端后就可以绕过了:

再利用Windows的系统命名来执行上传的脚本。

猜你喜欢

转载自blog.csdn.net/angry_program/article/details/105381900