DVWA-5.4 File Upload(文件上传)-Impossible

Impossible Level

查看源码

<?php

if( isset( $_POST[ 'Upload' ] ) ) {
    // Check Anti-CSRF token----校验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;//重写文件名(在文件名前面加id再整体md5)
    $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?
   // 校验文件类型及大小
   // 文件名后缀必须为jpg、jpeg、png之一,
   // 并且文件大小必须小于100000B约为97.7KB,
   // 并且文件类型必须为image/jpeg或image/png
   // 并且可以使用getimagesize()返回图像的大小和类型(注意,这个函数本身是不安全的)
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! $html .= "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>"; } else { // No $html .= '<pre>Your image was not uploaded.</pre>'; } // Delete any temp files if( file_exists( $temp_file ) ) unlink( $temp_file ); } else { // Invalid file $html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>'; } } // Generate Anti-CSRF token generateSessionToken(); ?>

相关函数介绍

substr(string,start,length)

函数返回字符串的一部分

strrpos(string,find,start)

函数查找字符串在另一字符串中最后一次出现的位置(区分大小写)

uniqid()

函数基于以微秒计的当前时间,生成一个唯一的 ID

in_get(varname)

函数返回相应选项的值

imagecreatefromjpeg ( filename )

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

imagejpeg ( image , filename , quality)

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

imagedestroy( img )

函数销毁图像资源

getimagesize()

函数用来获取图像的大小和类型

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

扩展

源码中使用了一个非常不安全的的函数:getimagesize()

getimagesize()函数会对目标文件的16进制去进行一个读取,去读取头几个字符串是不是符合图片的要求的。

getimagesize()返回结果中有文件大小和文件类型,如果用这个函数来获取类型,从而判断是否是图片的话,会存在问题。因为图片头可以被伪造,我们完全可以通过伪造正确的图片头来绕过它对图片类型的检查。

这就是图片的十六进制,前几位都是一样的

按照这样的逻辑,我们就可以去伪造一个假图片,让函数以为我们这就是图片,达到绕过的目的。

  • 方法1 直接伪造头部GIF89A
  • 方法2 CMD:copy /b test.png+munma.php hack.png
  • 方法3 使用GIMP(开源的图片修改软件),通过增加备注,写入执行命令。

但是,即使我们可以根据上述方法绕过函数getimagesize(),我们也绕不过imagecreatefromjpeg ( filename )、imagejpeg ( image , filename , quality)、imagedestroy( img )等几个函数对我们上传图片的重塑。所以,Impossible等级的代码是比较安全的。

参考:

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

https://blog.csdn.net/weixin_43915842/article/details/90183305

原文地址:https://www.cnblogs.com/zhengna/p/12764718.html