前言:
这套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>服務器操作系統: .................................................................... '.PHP_OS.'</p>';
echo '<p>Web 服務器: .................................................... '.$_SERVER['SERVER_SOFTWARE'].'</p>';
echo '<p>PHP 版本: .................................................................... '.PHP_VERSION.'</p>';
echo '<p>MySQL 版本: .................................................................... '.$sys_info['mysql_ver'].'</p>';
echo '<p>Zlib 支持: .................................................................... '.$sys_info['zlib'].'</p>';
echo '<p>GD2 支持: .................................................................... '.$sys_info['gd'].'</p>';
echo '<p>Socket 支持: .................................................................... '.$sys_info['socket'].'</p>';
echo '<p>curl 支持: .................................................................... '.$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]." ................................................................... <font color='#00CC33'>可寫</font></p>";
}
elseif ( $check_writeable == '0' ) {
echo "<p>".$check_dirs[$i]." ................................................................... <font color='#ff0000'>不可寫</font></p>";
$no_write = true;
}
elseif ( $check_writeable == '2' ) {
echo "<p>".$check_dirs[$i]." ................................................................... <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
的时候进行有效过滤即可。