系統環境
因為網上關于dvwa的相關資料都比較舊,所以想重新過一邊dvwa的各個類型的漏洞,順帶初步入門php代碼審計相關的知識。環境安裝可以直接參照網上教程新手指南:手把手教你如何搭建自己的滲透測試環境,已經寫的非常詳細,可以直接按照教程一步步安裝。
簡介
Command Injection,即命令注入,是指通過提交惡意構造的參數破壞命令語句結構,從而達到執行惡意命令的目的。PHP命令注入攻擊漏洞是PHP應用程序中常見的腳本漏洞之一,國內著名的Web應用程序Discuz! DedeCMS等都曾經存在過該類型漏洞。
low服務端核心代碼
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// 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>";
}
?>
相關函數介紹
stristr(string,search,before_search)
stristr函數搜索字符串在另一字符串中的第一次出現,返回字符串的剩余部分(從匹配點),如果未找到所搜索的字符串,則返回FALSE。參數string規定被搜索的字符串,參數search規定要搜索的字符串(如果該參數是數字,則搜索匹配該數字對應的ASCII值的字符),可選參數before_true為布爾型,默認為false,如果設置為true,函數將返回search參數第一次出現之前的字符串部分。
php_uname(mode)
這個函數會返回運行php的操作系統的相關描述,參數mode可取值”a” (此為默認,包含序列”s n r v m”里的所有模式),”s ”(返回操作系統名稱),”n”(返回主機名),” r”(返回版本名稱),”v”(返回版本信息), ”m”(返回機器類型)。可以看到,服務器通過判斷操作系統執行不同ping命令,但是對ip參數并未做任何的過濾,linux可以直接使用&&和;來執行多條命令,導致了嚴重的命令注入漏洞。
直接 提交 127.0.0.1&&ls -l 或把 ls -l 改為其他的系統命令也是可以執行的
Linux下輸入127.0.0.1&&cat /etc/shadow甚至可以讀取shadow文件。
Medium服務器核心代碼
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// Set blacklist
$substitutions = array(
'&&' => '',
';' => '',
);
// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
// 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>";
}
?>
對比low的代碼,服務端對ip參數做了一定的過濾,即將“&&”和“;”刪除,本質上采用了黑名單的方式過濾了一些字符,但是黑名單的模式永遠都會存在過濾不全的問題。命令執行同樣還是||的方式。所以我們可以采用的方式有很多種。
ping 127.0.0.1 || ls 當前面的一條命令執行失敗的時候執行后面一條命令
127.0.0.1 & ls &&的是前面一條命令必須執行成功才會執行后面一條命令,&不管前面的命令是否執行成功后面的命令都會執行
127.0.0.1 &;& ls -l 看代碼因為“&&” “;”會被替換成空字符,所以“;”被替換成空字符,命令成功執行。
high服務器核心代碼
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = trim($_REQUEST[ 'ip' ]);
// Set blacklist
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
// 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>";
}
?>
可以看到在medium的基礎上進一步過濾了& ; | - $ ( ) ` ||一些字符,進一步完善了黑名單機制,但是依然可以繞過。可以看到黑名單機制中‘| ’中后面有一個空格,于是這里又成了一個可以利用的地方。
127.0.0.1|ls -l |是管道符,表示將cmd1的輸出作為cmd2的輸入,并只打印cmd2執行的結果。
impossible服務器核心代碼
<?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();
?>
相關函數介紹
stripslashes(string)
stripslashes函數會刪除字符串string中的反斜杠,返回已剝離反斜杠的字符串。
explode(separator,string,limit)
把字符串打散為數組,返回字符串的數組。參數separator規定在哪里分割字符串,參數string是要分割的字符串,可選參數limit規定所返回的數組元素的數目。
is_numeric(string)
檢測string是否為數字或數字字符串,如果是返回TRUE,否則返回FALSE。
可以看到,Impossible級別的代碼加入了Anti-CSRF token,同時對參數ip進行了嚴格的限制,只有諸如“數字.數字.數字.數字”的輸入才會被接收執行,因此不存在命令注入漏洞。