網(wǎng)站上傳漏洞的前提是了解文件上傳這個功能嗎?
2021-10-17
PHP代碼審計:(一)文件上傳0x00概覽
在網(wǎng)站運行過程中,不可避免地會更新網(wǎng)站的某些頁面或內(nèi)容,這時就需要使用網(wǎng)站上的文件上傳功能。如果對上傳的文件沒有限制,或者繞過了限制,則可能會使用該功能將可執(zhí)行文件和腳本上傳到服務器,從而進一步導致服務器崩潰。
可見php文件上傳代碼,了解上傳漏洞的前提是了解文件上傳的功能及其原理。如果只知道有文件上傳,而且可能有漏洞,那就和不知道一樣。
具體來說,用戶上傳的一些文件仍然是PHP腳本。用戶可以通過服務器直接訪問這些上傳到服務器上的PHP腳本,并且會執(zhí)行其中包含的一些命令。文件上傳功能如此強大,如果您的網(wǎng)站在文件上傳方面沒有很好的控制,它就會崩潰。
文件上傳漏洞的原因有很多,主要包括:
其中開源編輯器漏洞和文件上傳漏洞原理相同,只是多了一個編輯器。上傳時,我們的腳本仍然會被上傳。
松散過濾非常常見,我們將在以下示例中看到。比如大小寫問題,網(wǎng)站只驗證是否是小寫,我們可以把后綴名改成大寫。
然后是文件解析漏洞。比如系統(tǒng)會涉及到這種情況:文件名為1.php;.jpg,IIS 6.0 可能認為是jpg文件,但是當它執(zhí)行被執(zhí)行。我們可以利用這個解析漏洞進行上傳。再比如php文件上傳代碼,有一些未知的后綴,比如a.php.xxx。由于后綴名無法識別,可能會被釋放。如果攻擊者再次執(zhí)行該文件,則該網(wǎng)站可能被控制。
最后是路徑截斷,就是在上傳的文件中使用一些特殊的符號,使文件在上傳時被截斷。比如a.php.jpg,在網(wǎng)站上驗證時,后綴會被認為是jpg,但保存到硬盤時,會被截斷為a.php,這是一個直接的php文件。
通常用于截斷路徑的字符有:
這些是可能導致截斷的字符。需要注意的是,在實戰(zhàn)中,由于網(wǎng)站的編解碼規(guī)則不同,需要靈活應用。例如,\0 失敗可以替換,或者你可以嘗試各種編碼,例如,或者,多試幾次。
0x01 代碼
文件上傳首先需要一個表單,如下,我們稱之為a.html:
這里有幾個要素:
接下來是PHP腳本中的東西。在 PHP 中,通過 $ 對象讀取文件,并使用以下屬性:
t.php 中的代碼是這樣寫的:
可以看到aaa是文件輸入框中的name屬性。
我們把這兩個文件放在服務器的目錄下,或者直接在目錄下啟動PHP自帶的服務器。打開a.html,上傳文件后,會得到這樣的結果。這里我直接上傳了a.html:
array(5) {
["name"]=> string(6) "a.html"
["type"]=> string(9) "text/html"
["tmp_name"]=> string(44) "C:\Users\asus\AppData\Local\Temp\php43A1.tmp"
["error"]=> int(0)
["size"]=> int(133)
}
需要說明的是,在處理文件上傳的時候,不要相信文件類型的類型,因為瀏覽器生成后類型是可以改變的。您甚至可以手動構建類型與實際內(nèi)容不匹配的數(shù)據(jù)包。
同時,不應信任文件名名稱。相反,文件名和擴展名應該分開,并且擴展名應該被列入白名單。文件名根據(jù)需要丟棄并重新生成,或過濾并重新使用。
過度相信這些東西會產(chǎn)生一些隱患,我們將在下面看到。
0x02 實戰(zhàn)
實戰(zhàn)部分,我會用DVWA中的例子來演示。DVWA是一套用PHP+編寫的WEB漏洞測試程序,用于常規(guī)WEB漏洞教學和檢測。包含SQL注入、XSS、盲注等常見安全漏洞。項目主頁在這里,源碼也在這里。
下載部署后,我們打開///,這里是上傳漏洞部分的源碼,可以看到難度分為低、中、高三個級別。
先看低級難度low.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
$html .= 'Your image was not uploaded.
';}else {// 是的!$html .=”
{$target_path} succesfully uploaded!
";}}
可以看到?jīng)]有過濾,可以直接上傳任何文件。
然后是.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
$html .= 'Your image was not uploaded.
';}else {// 是的!$html .=”
{$target_path} succesfully uploaded!
";}}else {// file$html .= '
Your image was not uploaded. We can only accept JPEG or PNG images.
';}}
注意第10行和第11行,驗證類型必須是jpg或png,大小必須小于某個值。后者可以忽略。剛才我說類型不可信,我們可以抓包,改類型,然后提交。
因為我想演示如何突破上傳限制,而不是如何使用上傳的腳本,所以我直接創(chuàng)建了一個新的PHP文件,并在其中寫入了一些內(nèi)容。打開抓包,我們會在請求體中看到類似這樣的內(nèi)容:
------WebKitFormBoundaryh4zhLV52OKhf6aJg
Content-Disposition: form-data; name="uploaded"; filename="a.php"
Content-Type: application/octet-stream
右鍵單擊“發(fā)送到”并將該 /- 更改為 /jpeg。點擊“前往”發(fā)送。
最后,高級high.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
$html .= 'Your image was not uploaded.
';}else {// 是的!$html .=”
{$target_path} succesfully uploaded!
";}}else {// file$html .= '
Your image was not uploaded. We can only accept JPEG or PNG images.
';}}
觀察同樣的位置,這次改用后綴名來判斷。這時候我們可以在后綴前插入\0,即a.php\0.jpg。請注意,它不是斜線加零,而是空字符。這樣判斷的時候,后綴是.jpg,寫入磁盤時會被截斷為a.php。它可以被上傳或執(zhí)行。
我們還捕獲包裹并交付。先把a.php改成a.php.jpg,然后切換到十六進制編輯模式插入空字符。
鼠標拖動的范圍是a.php.jpg,在第二個2e網(wǎng)格上右擊,點擊“byte”,會自動插入一個\0。然后點擊“開始”,你就完成了。
注意這里插入的話,會上傳成功,但是訪問的時候會直接當作圖片處理,里面的內(nèi)容不會被執(zhí)行。
0x03 解決方案
同目錄下還有一個.php,里面有正確的做法。大家可以看看。里面的代碼使用了$=md5(().$).'.'。$; 生成獨立的文件名,使其不受原文件名中各種截斷字符的干擾。
0x04 注意