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截斷問題。這里不詳加贅述。文章如有錯誤,歡迎大家指正。

   

 

    

 

 

    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM