install.php reinstall vulnerability in PHP code audit

Preface:

This set of php source code was sent to me by the teacher in the previous class, which contains a lot of conventional loopholes. I feel that it is very suitable for beginners to audit (my php chicken). Today's article is also audited before, and I feel that it has practical meaning, so I will sort out and update it to csdn, which is also for the convenience of the majority of basic friends to learn.

Audit steps:

The following is the code of the install.php file:

<?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>System environment</h1>';
echo '<p>Server operating system:&nbsp;....................................................................&nbsp;'.PHP_OS.'</p>';
echo '<p>Web Server:&nbsp;....................................................&nbsp;'.$_SERVER['SERVER_SOFTWARE'].'</p>';
echo '<p>PHP Edition:&nbsp;....................................................................&nbsp;'.PHP_VERSION.'</p>';
echo '<p>MySQL Edition:&nbsp;....................................................................&nbsp;'.$sys_info['mysql_ver'].'</p>';
echo '<p>Zlib Support:&nbsp;....................................................................&nbsp;'.$sys_info['zlib'].'</p>';
echo '<p>GD2 Support:&nbsp;....................................................................&nbsp;'.$sys_info['gd'].'</p>';
echo '<p>Socket Support:&nbsp;....................................................................&nbsp;'.$sys_info['socket'].'</p>';
echo '<p>curl Support:&nbsp;....................................................................&nbsp;'.$sys_info['curl_init'].'</p>';
echo '<h1>Directory permissions</h1>';

/* check directories */
$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 '> writable“;
	}
	elseif ( $check_writeable == '0' ) {
		echo "<p>".$check_dirs[$i]."&nbsp;...................................................................&nbsp;<font color='#Ff0000 '> not writable“;
		$no_write = true;
	}
	elseif ( $check_writeable == '2' ) {
		echo "<p>".$check_dirs[$i]."&nbsp;...................................................................&nbsp;<b>Non-existent</b></p>";
		$no_write = true;
	}
	$i = $i + 1;
}

if ( $sys_info['gd'] == 'NO' || $sys_info['curl_init'] == 'NO' ) {
	exit( 'The build is not supported and cannot be installed and used!' );
}else if ( $check_writeable == '0' || $check_writeable == '2' ) {
	exit( 'The key directory is not writable and cannot be installed and used!' );
}

if ( $_POST ) {

	if ( $_POST["dbhost"] == "" ) {
		exit( 'Database connection address cannot be empty' );
	}elseif ( $_POST["dbuser"] == "" ) {
		exit( 'Database database login' );
	}elseif ( $_POST["dbname"] == "" ) {
		exit( 'Please create the database name first' );
	}

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

	$con = mysql_connect( $dbhost, $dbuser, $dbpass );
	if ( !$con ) {
		die( 'Database link error, please check whether account password and address are correct: ' . 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('Database already exists')){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 );

	//Create table
	mysql_select_db( $dbname, $con );
	mysql_query( "set names 'utf8'", $con );
	//Import database
	$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('Successful installation')){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'>Database connection address:</div></td>";
	echo "<td><input name='dbhost' type='text' id='input' value='localhost'/> *</td>";
	echo "</tr>";
	echo "<tr>";
	echo "<td><div align='right'>Database login:</div></td>";
	echo "<td><input name='dbuser' type='text' id='input' value='root'/> *</td>";
	echo "</tr>";
	echo "<tr>";
	echo "<td><div align='right'>Database login password:</div></td>";
	echo "<td><input name='dbpass' type='password' id='input' value='root'/> *</td>";
	echo "</tr>";
	echo "<tr>";
	echo "<td><div align='right'>Create database name:</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='Installation' /></td>";
	echo "</tr>";
	echo "</table>";
	echo "</form>";
}
?>


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

Through a simple audit, we know whether the file header determines whether the current lock file / sys/install.lock exists. If it exists, it will return to the home page through the header ("location:.. / index. PHP"), but it does not exit through exit(), so this is also a precondition for vulnerability triggering.
The code is as follows:

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

}

Next, through code audit, we know that we have customized the check_writeable() function to determine whether the directory has read and write permissions, and then enumerate some common parameters of the server, etc., which is directly ignored here.
Then it comes to the following step:

if ( $_POST ) {

	if ( $_POST["dbhost"] == "" ) {
		exit( 'Database connection address cannot be empty' );
	}elseif ( $_POST["dbuser"] == "" ) {
		exit( 'Database database login' );
	}elseif ( $_POST["dbname"] == "" ) {
		exit( 'Please create the database name first' );
	}

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

	$con = mysql_connect( $dbhost, $dbuser, $dbpass );
	if ( !$con ) {
		die( 'Database link error, please check whether account password and address are correct: ' . 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('Database already exists')){window.history.back(-1);}</script>";
		exit();
	}

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

The general meaning is to determine whether there are $dbhost & $dbuser & $dbpass & $dbname and other parameters submitted by $\POST, connect to the database and determine whether the new database name already exists.
Next, create the database name through mysql_query ("create database $dbname", $con) or die (mysql_error()); note that (the parameter $dbname passed in from the outside) is directly brought into the sql statement without any filtering. (this is also a necessary condition for vulnerability triggering)
Next, execute the following statement:

	$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 );

The above code roughly means that the content of the $str_tmp file is written to config.php, and the $str_tmp contains the information when the database is created, such as $dbname.
Just now, the above analysis also shows that $dbname is not filtered when it is passed in externally, so we can change the value of $dbname to testva;--";phpinfo()//
Brought into the sql statement is mysql_query ("create database testva; --"; phpinfo(); / / ", $con) or die (mysql_error());
//Comment out the following statements, -- means to comment other statements in the myslq statement. The current statement means to create a aaa database, which can be executed successfully.
The following is a list of successful cases:
Check that phpinfo() constructed by source code config.php is written successfully.
Then visit config.php:

Repair plan:

After judging whether there is a lock file, directly add exit() to end the execution of the following php statement, and effectively filter when creating a new $dbname.

Published 20 original articles, won praise 29, visited 70000+
Private letter follow

Tags: Database PHP SQL zlib

Posted on Thu, 06 Feb 2020 01:57:45 -0500 by postmanager