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代码真的是很奇葩,变量覆盖的锅依旧有很多系统在背着啊~

源链接

Hacking more

...