zoukankan      html  css  js  c++  java
  • Web开发安全之文件上传安全

      很长一段时间像我这种菜鸡搞一个网站第一时间反应就是找上传,找上传。借此机会把文件上传的安全问题总结一下。

      首先看一下DVWA给出的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. 取文件最后的扩展名。
      $uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);   
    2. 对上传文件的文件名做随机数重命名操作,DVWA用的是MD5,rand()函数也可以。
       $target_file   =  md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
    3. 采取白名单方式验证文件的后缀名,MIME-TYPE类型,以及文件大小。
          if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) && 
              ( $uploaded_size < 100000 ) && 
              ( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' 
    4. 至关重要的一点,检查是否为真正图片。
      getimagesize( $uploaded_tmp )\ 若非图片,则返回一条Flase消息。
    5. GD库或image-magick进行二次渲染,洗掉图片中的恶意代码。
      $img = imagecreatefromjpeg( $uploaded_tmp ); 
    6. 采用相对路径回显到前端页面。
       if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) )
    • 那些年程序员跟我一起踩过的雷(应用开发常见的错误,对照上文开发流程)
    1. JavaScript前端验证文件类型

        不吹不黑,除了一些自己做过的政企站,还是一些临时页面。互联网行业还真没有这么写的。简而言之,就是把文件类型通过JavaScript代码验证文件类型。正确通过,错误跳一个alert弹窗。至于怎么绕不多赘述了,F12、burp大法好。小学生错误,不多赘述。

      2. 上传文件黑名单,不验证MIME-TYPE类型。

        保证安全的文件上传一定要用白名单,同时要验证MIME-TYPE类型。普通的黑名单bypass不过多赘述,大家都比较了解。印象比较深的就是某第三方开发软件,通过黑名单验证的上传文件类型而非白名单。结果jspx这个文件没有被黑名单包含,加之Tomcat6.0默认配置文件能正常解析jspx,直接服务器权限就被拿掉了,剩下做的说多了都是泪。

      3. 不验证是否为真正的图片文件。

        仅仅验证后缀名和MIME-TYPE类型是无法判断是否为真正的文件。这时候PHP中主要通过getimagesize()来分辨图片。首先要说一下文件幻数:

          打开winhex我们可以看到,不同图片格式的二进制流是一致的。

          例如GIF文件就是GIF89a,新建了一个.gif文件,通过Notepad++编辑如下:

    GIF89a
    (...some binary data...)
    <?php phpinfo(); ?>
    (... skipping the rest of binary data ...)
    

        我们用winhex打开相关文件可以看出:

        

        我们再使用getimagesize()函数获取并echo一下相关的变量值。

          

        如果不使用,文件幻数头:

        

             重复上述实验,返回false。也就是说在验证了后缀名白名单,MIME-TYPE以及图片幻数后,我们能确保上传的文件一定是一个图片。然而,还有种传说中的东西没法防御。图片马+解析漏洞,或者图片马+包含漏洞。

       4. 图片二次渲染

       通过GD库的imagecreatefromjpeg()函数,我们可以洗掉文件中的一句话木马,或者恶意代码。保证文件二进制流中,不包含恶意代码。这对解析漏洞或者包含漏洞有着非常不错的防御作用。

       5. 不限制上传覆盖.htacess文件

       如果不限制上传覆盖.htaccess文件,我们上述的所有努力都可能白费。

    • 总结:

       本篇仅仅从代码设计层面去考虑文件上传的安全性,未涉及相关的运维安全问题。例如Nginx与Apache的解析漏洞也应该在防御考虑当中。以及PHP所产生的00截断问题。这里不详加赘述。文章如有错误,欢迎大家指正。

       

        

     

        

  • 相关阅读:
    Linux_文件系统、磁盘分区_RHEL7
    Linux_LVM、RAID_RHEL7
    Linux_LVM、RAID_RHEL7
    Linux_系统时间管理
    简单聊聊HDFS RBF第二阶段工作近期的一些进展
    LinkedBlockingQueue和ArrayBlockingQueue之间的比较
    LinkedBlockingQueue和ArrayBlockingQueue之间的比较
    公司如何使用开源软件
    公司如何使用开源软件
    ListenableFuture和CompletableFuture简单小结
  • 原文地址:https://www.cnblogs.com/Hyber/p/6133196.html
Copyright © 2011-2022 走看看