0x00 前言
很久没审计了,在A5拉了套源码看看,发现了几点问题,感觉还挺有趣的,发出来看看。
0x01 奇葩的install.php
系统是用了全局变量注册,有很多的过滤函数,没细看。
顺着install.php代码走一遍,先是include了很多必要的文件。
@include './install/langs/blangs.cac.php';
@include './install/langs/ilangs.cac.php';
@include './install/install.fun.php';
@include './base.inc.php';
@include './include/mysql.cls.php';
其中有一个叫base.inc.php的文件,后来发现是存放数据库配置的文件。
默认的时候长这样:
跟着往下走,在判断install.lock的时候,代码如下:
f(!isset($dbhost) || !isset($ckpre)){
...
}elseif(file_exists($lockfile)){
$ierror = lang('lockexist');
}elseif(!class_exists('cls_mysql')){
...
}
调用了个lang方法,返回结果赋给ierror变量。
function lang($str=''){
global $langs;
$result = '';
$arr = explode(' ',$str);
if(empty($arr)) return '';
foreach($arr as $var){
$var = trim($var);
$result .= isset($langs[$var]) ? $langs[$var] : $var;
}
return $result;
}
可以看到方法lang里面没有exit掉,代码还会继续走。如果后面没有判断好,就是个典型的没有exit导致的重装?
继续看下面的代码,我缩减了一下代码,整体结构如下:
if($step == 1){
...
}elseif($step == '2'){
...
}elseif($step == '3'){
...
}elseif($step == '4'){
...
}elseif($step == '5'){
if($write_error){
$readonly = 1;
$ierror = './base.inc.php'.lang('forbidwrite');
}else $readonly = 0;
if($_POST['saveconfig']) {
$dbhost = setconfig($_POST['dbhost']);
$dbuser = setconfig($_POST['dbuser']);
$dbpw = setconfig($_POST['dbpw']);
$dbname = setconfig($_POST['dbname']);
$adminemail = setconfig($_POST['adminemail']);
$tblprefix = setconfig($_POST['tblprefix']);
if(empty($dbname)){
$ierror = lang('please input database cname');
}else{
if(!@mysql_connect($dbhost, $dbuser, $dbpw)){
$ierror = lang('dberror'.mysql_errno());
}else{
if(mysql_get_server_info() > '4.1'){
mysql_query("CREATE DATABASE IF NOT EXISTS `$dbname` DEFAULT CHARACTER SET $dbcharset");
}else mysql_query("CREATE DATABASE IF NOT EXISTS `$dbname`");
if(mysql_errno()) $ierror = lang('dberror'.mysql_errno());
mysql_close();
}
}
if(preg_match("/[^a-zA-Z_0-9]+/",$tblprefix)) $ierror = lang('pointed tblprefix illegal');
if(!$ierror){
$fp = fopen('./base.inc.php','r');
$configfile = fread($fp, filesize('./base.inc.php'));
fclose($fp);
$configfile = preg_replace("/[$]dbhost\s*\=\s*[\"'].*?[\"'];/is", "\$dbhost = '$dbhost';", $configfile);
$configfile = preg_replace("/[$]dbuser\s*\=\s*[\"'].*?[\"'];/is", "\$dbuser = '$dbuser';", $configfile);
$configfile = preg_replace("/[$]dbpw\s*\=\s*[\"'].*?[\"'];/is", "\$dbpw = '$dbpw';", $configfile);
$configfile = preg_replace("/[$]dbname\s*\=\s*[\"'].*?[\"'];/is", "\$dbname = '$dbname';", $configfile);
$configfile = preg_replace("/[$]adminemail\s*\=\s*[\"'].*?[\"'];/is", "\$adminemail = '$adminemail';", $configfile);
$configfile = preg_replace("/[$]tblprefix\s*\=\s*[\"'].*?[\"'];/is", "\$tblprefix = '$tblprefix';", $configfile);
$configfile = preg_replace("/[$]ckpre\s*\=\s*[\"'].*?[\"'];/is", "\$ckpre = '".random(3)."_';", $configfile);
$fp = fopen('./base.inc.php', 'w');
fwrite($fp, trim($configfile));
fclose($fp);
redirect("$installfile?step=6");
}
}
ins_header(1);
echo "<table width=\"95%\" cellspacing=\"1\" bgcolor=\"#D0DBE7\" border=\"0\" align=\"center\">\n";
trheader(array(lang('setting item'),lang('setting value'),lang('guide'),));
trbasic(array(lang('database server'),input_str('dbhost',$dbhost,'text',30,$readonly),lang('dbhost_guide')),0);
trbasic(array(lang('database user'),input_str('dbuser',$dbuser,'text',30,$readonly),lang('dbuser_guide')),0);
trbasic(array(lang('database password'),input_str('dbpw',$dbpw,'password',30,$readonly),lang('dbpw_guide')),0);
trbasic(array(lang('database cname'),input_str('dbname',$dbname,'text',30,$readonly),lang('dbname_guide')),0);
trbasic(array(lang('system email'),input_str('adminemail',$adminemail,'text',30,$readonly),lang('email_guide')),0);
trbasic(array(lang('tblprefix'),input_str('tblprefix',$tblprefix,'text',30,$readonly),lang('tblprefix_guide')),0);
echo "</table>\n";
ins_mider();
hidden_str('step',5);
hidden_str('saveconfig',1);
button_str('submit',lang('continue'));
ins_footer();
$ierror && ins_message($ierror);
}
看到step=5的时候,程序接收用户输入的配置信息,测试数据库连接是否正确(这里我们可以采用外连数据库的方式绕过),接下来将配置信息写入到base.inc.php。
但在写入的时候会判断一下ierror变量是否为空,为空才进行写入配置文件的操作。又因为已经装过了一次,在判断install.lock文件的时候ierror变量就已经存在信息了。所以这里就执行不到写入文件的代码了。
接着往下看的时候,又发现程序会把输入的数据库配置信息打印出来,因为前面已经包含了base.inc.php,所以这里打印的其实就是之前用户安装时候配置的信息。
给个官网demo截图,如下:
继续看下面,在step=6的时候,创建管理员账号,其中有一串代码是这样的
if(!$ierror){
redirect("$installfile?step=7&username=".rawurlencode($username)."&email=".rawurlencode($email)."&password=".md5(md5($password1)));
}
在判断没有任何问题的时候又把信息发给了step=7,而step=7在接收到信息,还是没有创建。。。
代码来到最后,终于是要创建数据库相关的了。
把整体理一下,虽然前面我们没办法通过配置文件来写shell,但后面我们可以试着插入一个管理员账号。
但想象是美好的,现实是残酷的,报错了。
去看了一下sql文件才发现,原来这家伙没有判断table是否存在,如果存在就drop掉。(之前看了很多代码都是会有这样的判断的,一般为了方便用户重装)
额,目的虽然没有达成,但这个代码真的是实属奇葩。。一路审一路笑,同事快把我当成傻子了。。。
0x02 sql注入
虽然addslashes了,但结合着全局变量注册都不是问题。
首先看到这个general.inc.php,是整个系统的核心处理文件,其中有这么一串代码
在注入变量之前,引入了base.inc.php,之前我们知道这是一个数据库配置文件。其中有一个变量叫做tblprefix,故名思议就是表名前缀了。
因为引入在前,那么一个很完美的变量覆盖。全局查找一下这个$tblprefix在哪些地方用到:
还是挺多的,但比较好利用的可能比较少。我找了一个,vote.php
看到代码:
表名前缀在这么一个位置,完美的注入点。
而且系统是开启了报错的,不知道是我的环境问题还是怎么样,但如果是我的环境问题还是可以开启报错的
因为还是可以通过变量覆盖的方式来开启报错,这个phpviewerror是在base.inc.php中的。
最后成功报错注入:
0x03 总结
这套系统的install.php代码真的是很奇葩,变量覆盖的锅依旧有很多系统在背着啊~