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