DVWA-File_Upload

Vulnerability: File Upload

Intruduce

文件上传漏洞

形成的主因:

由于对上传文件类型未验证或过滤机制不严,导致恶意用户可以上传脚本文件,通过上传文件可达到控制网站权限的目的

危害:

攻击者可获得网站控制权限,查看、修改、删除网站数据,通过提权漏洞可获得主机权限

上传点:

头像、附件、后台新闻编辑等等

利用条件

成功上传木马文件
木马文件能够执行
上传路径可知

上传检查方式

A 客户端javascript 检测(通常为检测文件扩展名)
B 服务端MIME 类型检测(检测Content-Type 内容)
C 服务端目录路径检测(检测跟path 参数相关的内容)
D 服务端文件扩展名检测(检测跟文件extension 相关的内容)
E 服务端文件内容检测(检测内容是否合法或含有恶意代码)

常见上传漏洞

对文件类型无任何验证

可直接上传相应脚本文件

本地javascript扩展名校验

一切在客户端的校验都是伪校验

使用黑名单or白名单

扩展名定义方式对比

上传文件未进行重命名

结合web应用解析漏洞

客户端可控制上传参数

部分敏感上传参数在客户端可控

数据库备份

可通过备份功能修改文件后缀

检测绕过

检测绕过
停止客户端检测脚本
MIME类型检测绕过
黑名单列表绕过
特殊文件名绕过
利用截断
利用解析漏洞
利用.htaccess文件
可修改上传文件
黑名单检测绕过
1. 文件名大小写绕过

用AsP,pHp之类的文件名绕过黑名单检测。

2. 名单列表绕过

用黑名单里没有的名单进行攻击,比如黑名单里面没有asa或者cer之类。

3. 0x00截断绕过

例如:help.asp .jpg(asp后面为0x00),在判断时,大多函数取后缀名是从后往前取,故能够通过,但是在保存时,却被保存为help.asp。

4. .htaccess文件攻击

配合名单列表绕过,上传一个自定义的.htaccess。

Low

image

File Upload Source

<?php
if( isset( $_POST[ 'Upload' ] ) ) {
    // Where are we going to be writing to?
    $target_path  = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
    $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
    // Can we move the file to the upload folder?
    if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
        // No
        echo '<pre>Your image was not uploaded.</pre>';
    }
    else {
        // Yes!
        echo "<pre>{$target_path} succesfully uploaded!</pre>";
    }
}
?> 

分析

basename() 函数返回路径中的文件名部分。
PHP 的全局数组 $_FILES,你可以从客户计算机向远程服务器上传文件。
第一个参数是表单的 input name,第二个下标可以是 "name"、"type"、"size"、"tmp_name" 或 "error"。如下所示:
$_FILES["file"]["name"] - 上传文件的名称
$_FILES["file"]["type"] - 上传文件的类型
$_FILES["file"]["size"] - 上传文件的大小,以字节计
$_FILES["file"]["tmp_name"] - 存储在服务器的文件的临时副本的名称
$_FILES["file"]["error"] - 由文件上传导致的错误代码

对于上传的文件没有做任何过滤

漏洞利用

直接上传一句话
image

../../hackable/uploads/shell_exe.php
http://192.168.10.11/dvwa/hackable/uploads/shell_exe.php
http://192.168.10.11/dvwa/hackable/uploads/shell_exe.php?cmd=ipconfig

找到路径并利用

传一句话连接菜刀
image

Middle

File Upload Source

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
    // Where are we going to be writing to?
    $target_path  = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
    $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

    // File information
    $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
    $uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
    $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];

    // Is it an image?
    if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
        ( $uploaded_size < 100000 ) ) {

        // Can we move the file to the upload folder?
        if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
            // No
            echo '<pre>Your image was not uploaded.</pre>';
        }
        else {
            // Yes!
            echo "<pre>{$target_path} succesfully uploaded!</pre>";
        }
    }
    else {
        // Invalid file
        echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
    }
}

?> 

分析

关键代码部分

( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
    ( $uploaded_size < 100000 )

这里对文件类型了文件大小做了检查,要求Content-Type字段为image/jpeg 或 image/png ,内容的大小小于 100000

漏洞利用

image
抓包后将Content_Type字段改为image/jpeg即可
image

采用%00截断
在php版本小于5.3.4的版本中,当Magic_quote_gpc选项为off时,可以在文件名中使用%00截断,所以可以把上传文件命名a1.php%00.png进行绕过

High

File Upload Source

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
    // Where are we going to be writing to?
    $target_path  = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
    $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

    // File information
    $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
    $uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
    $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
    $uploaded_tmp  = $_FILES[ 'uploaded' ][ 'tmp_name' ];

    // Is it an image?
    if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
        ( $uploaded_size < 100000 ) &&
        getimagesize( $uploaded_tmp ) ) {

        // Can we move the file to the upload folder?
        if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
            // No
            echo '<pre>Your image was not uploaded.</pre>';
        }
        else {
            // Yes!
            echo "<pre>{$target_path} succesfully uploaded!</pre>";
        }
    }
    else {
        // Invalid file
        echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
    }
}

?> 

分析

关键代码分析

$uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
strrpos() 函数查找字符串在另一字符串中最后一次出现的位置(对大小写敏感)。

substr(string,start,length) 函数返回字符串的一部分(如果 start 参数是负数且 length 小于或等于 start,则 length 为 0)。

( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
    ( $uploaded_size < 100000 ) &&
    getimagesize( $uploaded_tmp )

getimagesize(string filename)
函数会通过读取文件头,返回图片的长、宽等信息,如果没有相关的图片文件头,函数会报错。

可以看到先读取文件名中最后一个”.”后的字符串,期望通过文件名来限制文件类型,因此要求上传文件名形式必须是”.jpg”、”.jpeg” 、”*.png”之一。同时,getimagesize函数更是限制了上传文件的文件头必须为图像类型。

漏洞利用

直接上传一个图片马

image

Impossible

File Upload Source

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
    // Check Anti-CSRF token
    checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );


    // File information
    $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
    $uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
    $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
    $uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
    $uploaded_tmp  = $_FILES[ 'uploaded' ][ 'tmp_name' ];

    // Where are we going to be writing to?
    $target_path   = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
    //$target_file   = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';
    $target_file   =  md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
    $temp_file     = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
    $temp_file    .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;

    // Is it an image?
    if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
        ( $uploaded_size < 100000 ) &&
        ( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
        getimagesize( $uploaded_tmp ) ) {

        // Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
        if( $uploaded_type == 'image/jpeg' ) {
            $img = imagecreatefromjpeg( $uploaded_tmp );
            imagejpeg( $img, $temp_file, 100);
        }
        else {
            $img = imagecreatefrompng( $uploaded_tmp );
            imagepng( $img, $temp_file, 9);
        }
        imagedestroy( $img );

        // Can we move the file to the web root from the temp folder?
        if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
            // Yes!
            echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
        }
        else {
            // No
            echo '<pre>Your image was not uploaded.</pre>';
        }

        // Delete any temp files
        if( file_exists( $temp_file ) )
            unlink( $temp_file );
    }
    else {
        // Invalid file
        echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
    }
}

// Generate Anti-CSRF token
generateSessionToken();

?> 

分析

magecreatefromjpeg ( filename )
函数返回图片文件的图像标识,失败返回false

imagecreatefromjpeg — 由文件或 URL 创建一个新图象。
imagejpeg ( image , filename , quality)

从image图像以filename为文件名创建一个JPEG图像,可选参数quality,范围从 0(最差质量,文件更小)到 100(最佳质量,文件最大)。

 imagedestroy( img )

函数销毁图像资源

可以看到,Impossible级别的代码对上传文件进行了重命名(为md5值,导致%00截断无法绕过过滤规则),加入Anti-CSRF token防护CSRF攻击,同时对文件的内容作了严格的检查,导致攻击者无法上传含有恶意脚本的文件

参考

渗透测试方法论之文件上传

DVWA之PHP文件上传漏洞(File Upload)