很長一段時間像我這種菜雞搞一個網站第一時間反應就是找上傳,找上傳。藉此機會把文件上傳的安全問題總結一下。php
首先看一下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(); ?>
咱們來分析一下文件安全上傳的流程:程序員
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
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 )\\ 若非圖片,則返回一條Flase消息。
$img = imagecreatefromjpeg( $uploaded_tmp );
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) )
不吹不黑,除了一些本身作過的政企站,仍是一些臨時頁面。互聯網行業還真沒有這麼寫的。簡而言之,就是把文件類型經過JavaScript代碼驗證文件類型。正確經過,錯誤跳一個alert彈窗。至於怎麼繞很少贅述了,F十二、burp大法好。小學生錯誤,很少贅述。web
2. 上傳文件黑名單,不驗證MIME-TYPE類型。安全
保證安全的文件上傳必定要用白名單,同時要驗證MIME-TYPE類型。普通的黑名單bypass不過多贅述,你們都比較瞭解。印象比較深的就是某第三方開發軟件,經過黑名單驗證的上傳文件類型而非白名單。結果jspx這個文件沒有被黑名單包含,加之Tomcat6.0默認配置文件能正常解析jspx,直接服務器權限就被拿掉了,剩下作的說多了都是淚。服務器
3. 不驗證是否爲真正的圖片文件。session
僅僅驗證後綴名和MIME-TYPE類型是沒法判斷是否爲真正的文件。這時候PHP中主要經過getimagesize()來分辨圖片。首先要說一下文件幻數:運維
打開winhex咱們能夠看到,不一樣圖片格式的二進制流是一致的。jsp
例如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截斷問題。這裏不詳加贅述。文章若有錯誤,歡迎你們指正。