php代碼審計之install.php重裝漏洞

前言:

這套php的源碼是之前上課的時候老師發給我的,裏面包含了很多的常規的漏洞,個人感覺非常適合初學者來進行審計(本人php菜雞),今天發的文章也是之前審計過的,個人感覺比較具有實戰意義,所以就整理更新到csdn上來,也是爲了方便廣大的基友們學習。

審計步驟:

下面貼出來的是install.php文件的代碼:

<?php

if ( file_exists($_SERVER["DOCUMENT_ROOT"].'/sys/install.lock') ) {
	header( "Location: ../index.php" );

}

require_once '../header.php';

function check_writeable( $file ) {
	if ( file_exists( $file ) ) {
		if ( is_dir( $file ) ) {
			$dir = $file;
			if ( $fp = @fopen( "$dir/test.txt", 'w' ) ) {
				@fclose( $fp );
				@unlink( "$dir/test.txt" );
				$writeable = 1;
			}
			else {
				$writeable = 0;
			}
		}
		else {
			if ( $fp = @fopen( $file, 'a+' ) ) {
				@fclose( $fp );
				$writeable = 1;
			}
			else {
				$writeable = 0;
			}
		}
	}
	else {
		$writeable = 2;
	}
	return $writeable;
}

$sys_info['mysql_ver']     = extension_loaded( 'mysql' ) ? 'OK' : 'NO';
$sys_info['zlib']          = function_exists( 'gzclose' ) ? 'OK' : 'NO';
$sys_info['gd']            = extension_loaded( "gd" ) ? 'OK' : 'NO';
$sys_info['socket']        = function_exists( 'fsockopen' ) ? 'OK' : 'NO';
$sys_info['curl_init']        = function_exists( 'curl_init' ) ? 'OK' : 'NO';

echo '<div id="ourhp_er">';
echo '<h1>系統環境</h1>';
echo '<p>服務器操作系統:&nbsp;....................................................................&nbsp;'.PHP_OS.'</p>';
echo '<p>Web 服務器:&nbsp;....................................................&nbsp;'.$_SERVER['SERVER_SOFTWARE'].'</p>';
echo '<p>PHP 版本:&nbsp;....................................................................&nbsp;'.PHP_VERSION.'</p>';
echo '<p>MySQL 版本:&nbsp;....................................................................&nbsp;'.$sys_info['mysql_ver'].'</p>';
echo '<p>Zlib 支持:&nbsp;....................................................................&nbsp;'.$sys_info['zlib'].'</p>';
echo '<p>GD2 支持:&nbsp;....................................................................&nbsp;'.$sys_info['gd'].'</p>';
echo '<p>Socket 支持:&nbsp;....................................................................&nbsp;'.$sys_info['socket'].'</p>';
echo '<p>curl 支持:&nbsp;....................................................................&nbsp;'.$sys_info['curl_init'].'</p>';
echo '<h1>目錄權限</h1>';

/* 檢查目錄 */
$check_dirs = array (
	'../sys',
	'../uploads'
);

$i = 0;
foreach ( $check_dirs as $dir ) {
	$full_dir = $dir;
	$check_writeable = check_writeable( $full_dir );
	if ( $check_writeable == '1' ) {
		echo "<p>".$check_dirs[$i]."&nbsp;...................................................................&nbsp;<font color='#00CC33'>可寫</font></p>";
	}
	elseif ( $check_writeable == '0' ) {
		echo "<p>".$check_dirs[$i]."&nbsp;...................................................................&nbsp;<font color='#ff0000'>不可寫</font></p>";
		$no_write = true;
	}
	elseif ( $check_writeable == '2' ) {
		echo "<p>".$check_dirs[$i]."&nbsp;...................................................................&nbsp;<b>不存在</b></p>";
		$no_write = true;
	}
	$i = $i + 1;
}

if ( $sys_info['gd'] == 'NO' || $sys_info['curl_init'] == 'NO' ) {
	exit( '組建不支持,無法安裝使用!' );
}else if ( $check_writeable == '0' || $check_writeable == '2' ) {
	exit( '關鍵目錄不可寫,無法安裝使用!' );
}

if ( $_POST ) {

	if ( $_POST["dbhost"] == "" ) {
		exit( '數據庫連接地址不能爲空' );
	}elseif ( $_POST["dbuser"] == "" ) {
		exit( '數據庫數據庫登錄名' );
	}elseif ( $_POST["dbname"] == "" ) {
		exit( '請先創建數據庫名稱' );
	}

	$dbhost = $_POST["dbhost"];
	$dbuser = $_POST["dbuser"];
	$dbpass = $_POST["dbpass"];
	$dbname = $_POST["dbname"];

	$con = mysql_connect( $dbhost, $dbuser, $dbpass );
	if ( !$con ) {
		die( '數據庫鏈接出錯,請檢查賬號密碼及地址是否正確: ' . mysql_error() );
	}

	$result = mysql_query('show databases;') or die ( mysql_error() );;
	While($row = mysql_fetch_assoc($result)){       
		$data[] = $row['Database'];
	}
	unset($result, $row);
	if (in_array(strtolower($dbname), $data)){
		mysql_close();
		echo "<script>if(!alert('數據庫已存在')){window.history.back(-1);}</script>";
		exit();
	}

	// exp;-- -";phpinfo();//

	mysql_query( "CREATE DATABASE $dbname", $con ) or die ( mysql_error() );

	$str_tmp="<?php\r\n";
	$str_end="?>";
	$str_tmp.="\r\n";
	$str_tmp.="error_reporting(0);\r\n";
	$str_tmp.="\r\n";
	$str_tmp.="if (!file_exists(\$_SERVER[\"DOCUMENT_ROOT\"].'/sys/install.lock')){\r\n\theader(\"Location: /install/install.php\");\r\nexit;\r\n}\r\n";
	$str_tmp.="\r\n";
	$str_tmp.="include_once('../sys/lib.php');\r\n";
	$str_tmp.="\r\n";
	$str_tmp.="\$host=\"$dbhost\"; \r\n";
	$str_tmp.="\$username=\"$dbuser\"; \r\n";
	$str_tmp.="\$password=\"$dbpass\"; \r\n";
	$str_tmp.="\$database=\"$dbname\"; \r\n";
	$str_tmp.="\r\n";
	$str_tmp.="\$conn = mysql_connect(\$host,\$username,\$password);\r\n";
	$str_tmp.="mysql_query('set names utf8',\$conn);\r\n";
	$str_tmp.="mysql_select_db(\$database, \$conn) or die(mysql_error());\r\n";
	$str_tmp.="if (!\$conn)\r\n";
	$str_tmp.="{\r\n";
	$str_tmp.="\tdie('Could not connect: ' . mysql_error());\r\n";
	$str_tmp.="\texit;\r\n";
	$str_tmp.="}\r\n";
	$str_tmp.="\r\n";
	$str_tmp.="session_start();\r\n";
	$str_tmp.="\r\n";
	$str_tmp.=$str_end;

	$fp=fopen( "../sys/config.php", "w" );
	fwrite( $fp, $str_tmp );
	fclose( $fp );

	//創建表
	mysql_select_db( $dbname, $con );
	mysql_query( "set names 'utf8'", $con );
	//導入數據庫
	$sql=file_get_contents( "install.sql" );
	$a=explode( ";", $sql );
	foreach ( $a as $b ) {
		mysql_query( $b.";" );
	}
	mysql_close( $con );
	file_put_contents($_SERVER["DOCUMENT_ROOT"].'/sys/install.lock', 'virink');
	echo "<script>if(!alert('安裝成功')){window.location.href='../index.php';}</script>";
	exit;
}else {
	echo "<form id='form1' name='form1' method='post' action=''>";
	echo "<table width='100%' border='0' align='center' cellpadding='10' id='table'>";
	echo "<tr>";
	echo "<td colspan='2'><h1></h1></td>";
	echo "</tr>";
	echo "<tr>";
	echo "<td><div align='right'>數據庫連接地址:</div></td>";
	echo "<td><input name='dbhost' type='text' id='input' value='localhost'/> *</td>";
	echo "</tr>";
	echo "<tr>";
	echo "<td><div align='right'>數據庫登錄名:</div></td>";
	echo "<td><input name='dbuser' type='text' id='input' value='root'/> *</td>";
	echo "</tr>";
	echo "<tr>";
	echo "<td><div align='right'>數據庫登錄密碼:</div></td>";
	echo "<td><input name='dbpass' type='password' id='input' value='root'/> *</td>";
	echo "</tr>";
	echo "<tr>";
	echo "<td><div align='right'>創建數據庫名稱:</div></td>";
	echo "<td><input name='dbname' type='text' id='input' value='vauditdemo'/> *</td> ";
	echo "</tr>";
	echo "<tr>";
	echo "<td></td>";
	echo "<td><input type='submit' class='btn' name='Submit' value='安裝' /></td>";
	echo "</tr>";
	echo "</table>";
	echo "</form>";
}
?>


<?php
require_once '../footer.php';
?>

通過簡單的審計得知文件頭部判斷是否存在當前鎖文件/sys/install.lock,如果存在則通過header("Location: ../index.php")返回到主頁,但是並沒有通過exit()進行退出,所以此處也是漏洞觸發的前提條件。
代碼如下:


if ( file_exists($_SERVER["DOCUMENT_ROOT"].'/sys/install.lock') ) {
	header( "Location: ../index.php" );

}

接下來通過代碼審計得知自定義了check_writeable()函數來判斷目錄是否具有讀寫權限,然後將服務器的一些常用參數列舉出來等等,這裏就直接忽略了。
然後就到了下面一步:

if ( $_POST ) {

	if ( $_POST["dbhost"] == "" ) {
		exit( '數據庫連接地址不能爲空' );
	}elseif ( $_POST["dbuser"] == "" ) {
		exit( '數據庫數據庫登錄名' );
	}elseif ( $_POST["dbname"] == "" ) {
		exit( '請先創建數據庫名稱' );
	}

	$dbhost = $_POST["dbhost"];
	$dbuser = $_POST["dbuser"];
	$dbpass = $_POST["dbpass"];
	$dbname = $_POST["dbname"];

	$con = mysql_connect( $dbhost, $dbuser, $dbpass );
	if ( !$con ) {
		die( '數據庫鏈接出錯,請檢查賬號密碼及地址是否正確: ' . mysql_error() );
	}

	$result = mysql_query('show databases;') or die ( mysql_error() );;
	While($row = mysql_fetch_assoc($result)){       
		$data[] = $row['Database'];
	}
	unset($result, $row);
	if (in_array(strtolower($dbname), $data)){
		mysql_close();
		echo "<script>if(!alert('數據庫已存在')){window.history.back(-1);}</script>";
		exit();
	}

	mysql_query( "CREATE DATABASE $dbname", $con ) or die ( mysql_error() );

大致的意思就是判斷是否存在$_POST提交過來的$dbhost&$dbuser&$dbpass&$dbname等參數,連接數據庫並且判斷新建的數據庫名是否已經存在。
接着就是通過mysql_query( "CREATE DATABASE $dbname", $con ) or die ( mysql_error() );來創建數據庫名,這需要注意的是(在外部傳入的參數$dbname)這裏是未做任何過濾就直接帶入了sql語句中的。(這裏也是漏洞觸發的必要條件)
接着執行下面的語句:

	$str_tmp="<?php\r\n";
	$str_end="?>";
	$str_tmp.="\r\n";
	$str_tmp.="error_reporting(0);\r\n";
	$str_tmp.="\r\n";
	$str_tmp.="if (!file_exists(\$_SERVER[\"DOCUMENT_ROOT\"].'/sys/install.lock')){\r\n\theader(\"Location: /install/install.php\");\r\nexit;\r\n}\r\n";
	$str_tmp.="\r\n";
	$str_tmp.="include_once('../sys/lib.php');\r\n";
	$str_tmp.="\r\n";
	$str_tmp.="\$host=\"$dbhost\"; \r\n";
	$str_tmp.="\$username=\"$dbuser\"; \r\n";
	$str_tmp.="\$password=\"$dbpass\"; \r\n";
	$str_tmp.="\$database=\"$dbname\"; \r\n";
	$str_tmp.="\r\n";
	$str_tmp.="\$conn = mysql_connect(\$host,\$username,\$password);\r\n";
	$str_tmp.="mysql_query('set names utf8',\$conn);\r\n";
	$str_tmp.="mysql_select_db(\$database, \$conn) or die(mysql_error());\r\n";
	$str_tmp.="if (!\$conn)\r\n";
	$str_tmp.="{\r\n";
	$str_tmp.="\tdie('Could not connect: ' . mysql_error());\r\n";
	$str_tmp.="\texit;\r\n";
	$str_tmp.="}\r\n";
	$str_tmp.="\r\n";
	$str_tmp.="session_start();\r\n";
	$str_tmp.="\r\n";
	$str_tmp.=$str_end;

	$fp=fopen( "../sys/config.php", "w" );
	fwrite( $fp, $str_tmp );
	fclose( $fp );

上面代碼大致的意思就是將$str_tmp文件內容寫入到config.php中,並且$str_tmp中是包含了數據庫新建時的信息的,比如$dbname等。
剛剛上面也進行了分析,$dbname在外部傳入的時候是沒有進行任何過濾的,所以我們可以將$dbname的值修改爲testva;--";phpinfo()//
帶入到sql語句中就是mysql_query( "CREATE DATABASE testva;-- ";phpinfo();//", $con ) or die ( mysql_error() );
//註釋掉後面的語句,--的意思就是在myslq語句中註釋後面的其他語句,那麼當前語句的意思就是創建一個aaa的數據庫,是可以成功執行的。
下圖是利用成功的案列:
在這裏插入圖片描述查看源代碼config.php 構造的phpinfo()是成功寫入的。
在這裏插入圖片描述隨後訪問config.php在這裏插入圖片描述

修復方案:

在判斷是否存在lock文件後,直接加入exit()來結束後面php語句的執行,並且在新建$dbname的時候進行有效過濾即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章