【90Sec Team】新年贺礼之——PHPCMS v9.6.0 SQL注入漏洞分析


Jun 14 2016

【90Sec Team】新年贺礼之——PHPCMS v9.6.0 SQL注入漏洞分析

首页 » 代码审计 » 【90Sec Team】新年贺礼之——PHPCMS v9.6.0 SQL注入漏洞分析   

【90Sec Team】新年贺礼之——PHPCMS v9.6.0 SQL注入漏洞分析

0x01 前言

  phpcms是国内相对用的较多的cms之一。曾几何时,其安全性虽说不上好,但开发人员愿意正视安全漏洞,并有对安全漏洞有合理、正确、不粗暴的处理方式。
但这一年,我可以明显感到phpcms已经慢慢在放弃维护,就安全漏洞而言,处理手段越来越粗暴。随处可见的safe_replace函数(整个phpcms里,非数字型的输入有大量用这个函数进行处理):
function safe_replace($string) {
    $string = str_replace('%20','',$string);
    $string = str_replace('%27','',$string);
    $string = str_replace('%2527','',$string);
    $string = str_replace('*','',$string);
    $string = str_replace('"','"',$string);
    $string = str_replace("'",'',$string);
    $string = str_replace('"','',$string);
    $string = str_replace(';','',$string);
    $string = str_replace('<','<',$string);
    $string = str_replace('>','>',$string);
    $string = str_replace("{",'',$string);
    $string = str_replace('}','',$string);
    $string = str_replace('\\','',$string);
    return $string;
}
虽说比部分cms直接拦截select、if要好很多,但这种方法也是粗暴不堪。大量搜索,搜不出有特殊字符的内容,就是这个函数在作怪。
我曾经跟一个朋友说过,当一个cms在惧怕安全问题,并开始用拦截、替换等粗暴方式处理漏洞的时候,就是它走向灭亡的开始。此类例子屡见不鲜,比如phpyun、cmseasy。
那么,这次这个安全漏洞是怎么出现的?

0x02 漏洞成因分析

  我们可以下载源码,对比查看v9.5.9 -> v9.5.10v9.5.10 -> v9.6.0的变化。
我们可以看到,在v9.5.10中,引入了一个新的类: db_mysqli.php。为什么phpcms要加这个类,其实从名字就可以看出,db_mysqli.php是对mysqli的一个封装。
在php5.5+以后,传统的mysql扩展就已经废弃了
14536600944797.jpg
 
 

 所以,phpcms也开始针对这个问题进行改进,这里引入mysqli这个封装类就是证明了这一点。不过,在v9.5.10中,默认使用的mysql封装类还是mysql:14536603500183.jpg

根据配置文件在db_factory类中加载:

14536602891464.jpg

而在v9.6.0版本中,默认的mysql封装类换成了mysqli:

14536607750930.jpg

 

 这里,就是造成漏洞的关键点。我们对比一下这两个封装类的connect函数。

/libs/classes/mysql.class.php:

 

public function connect() {
    $func = $this->config['pconnect'] == 1 ? 'mysql_pconnect' : 'mysql_connect';
    if(!$this->link = @$func($this->config['hostname'], $this->config['username'], $this->config['password'], 1)) {
        $this->halt('Can not connect to MySQL server');
        return false;
    }
 
    if($this->version() > '4.1') {
        $charset = isset($this->config['charset']) ? $this->config['charset'] : '';
        $serverset = $charset ? "character_set_connection='$charset',character_set_results='$charset',character_set_client=binary" : '';
        $serverset .= $this->version() > '5.0.1' ? ((empty($serverset) ? '' : ',')." sql_mode='' ") : '';
        $serverset && mysql_query("SET $serverset", $this->link);       
    }
 
    if($this->config['database'] && !@mysql_select_db($this->config['database'], $this->link)) {
        $this->halt('Cannot use database '.$this->config['database']);
        return false;
    }
    $this->database = $this->config['database'];
    return $this->link;
}

 

 

 

 

/libs/classes/db_mysqli.php

public function connect() {
     
    $this->link = new mysqli($this->config['hostname'], $this->config['username'], $this->config['password'], $this->config['database'], $this->config['port']?intval($this->config['port']):3306);
 
    if(mysqli_connect_errno()){
        $this->halt('Can not connect to MySQL server');
        return false;
    }
    if($this->version() > '4.1') {
        $charset = isset($this->config['charset']) ? $this->config['charset'] : '';
        $this->link->set_charset($charset);
        $serverset = $this->version() > '5.0.1' ? 'sql_mode=\'\'' : '';
        $serverset && $this->link->query("SET $serverset");
    }
    return $this->link;
}

有何区别?mysqldb_mysqli里多了一段:

if($this->version() > '4.1') {
        $charset = isset($this->config['charset']) ? $this->config['charset'] : '';
        $serverset = $charset ? "character_set_connection='$charset',character_set_results='$charset',character_set_client=binary" : '';
        $serverset .= $this->version() > '5.0.1' ? ((empty($serverset) ? '' : ',')." sql_mode='' ") : '';
        $serverset && mysql_query("SET $serverset", $this->link);       
    }


这段是干啥的?是设置character_set_client=binary。作用是防御宽字节注入的,在v9.6.0里居然忘记设置。
(至于宽字节注入的原理与防御方法,见 https://www.leavesongs.com/PENETRATION/mutibyte-sql-inject.html
这就是新版phpcms里产生注入的原因。


 

0x03 寻找利用途径

  这个漏洞点是存在于db_mysqli类中,这个类等于说是所有sql语句执行都会经过的类,所以我们只需找到一个满足以下条件的输入点:

 

1. 字符型输入(不经过intval)
2. 不经过safe_replace过滤
3. 最好无需登录
 

 

 在当前版本的phpcms中,因为大量位置经过了safe_replace过滤,所以这样的地方也不好找。不过去除第3个条件的话,位置还是比较多的。
我简单找了两个位置,


  一是 /modules/member/index.php:1473

 

public function public_forget_password () {
    $email_config = getcache('common', 'commons');
     
    //SMTP MAIL 二种发送模式
    if($email_config['mail_type'] == '1'){
        if(empty($email_config['mail_user']) || empty($email_config['mail_password'])) {
            showmessage(L('email_config_empty'), HTTP_REFERER);
        }
    }
    $this->_session_start();
    $member_setting = getcache('member_setting');
    if(isset($_POST['dosubmit'])) {
        if ($_SESSION['code'] != strtolower($_POST['code'])) {
            showmessage(L('code_error'), HTTP_REFERER);
        }
        $memberinfo = $this->db->get_one(array('email'=>$_POST['email']));
 可见这里直接将email传入get_one函数,一定存在注入。前面有个验证码的检测,实际上是可以绕过的,只要我们不传入code即可。14536621868959.jpg


 

  这个注入无需登录,但需要网站配置了email。
  另一个地方是需要登录的,
   /modules/attachment/attachments.php:155
public function swfdelete() {
    $attachment = pc_base::load_sys_class('attachment');
    $att_del_arr = explode('|',$_GET['data']);
    foreach($att_del_arr as $n=>$att){
        if($att) $attachment->delete(array('aid'=>$att,'userid'=>$this->userid,'uploadip'=>ip()));
    }
}
这里从GET中获取data,放进delete函数里了。
14536624719085.jpg
成功注入。

  总结一下,这些注入,根本原因是宽字符注入,新版phpcms里由于代码更新换代导致遗失了之前的安全设置,导致出现安全漏洞。
因为是宽字符注入,所以注入是存在于GBK版本的phpcms里,UTF-8版本是不存在这个漏洞的。
 

 

如果您喜欢本博客,欢迎点击图片定订阅到邮箱填写您的邮件地址,订阅我们的精彩内容:

正文部分到此结束

文章标签: PHPCMS PHPCMS漏洞 PHPCMS注入 bug

版权声明:若无特殊注明,本文皆为( mOon )原创,转载请保留文章出处。

也许喜欢: «Struts2-037(含poc) | phpwind 利用哈希长度扩展攻击进行getshell»

你肿么看?

你还可以输入 250/250 个字

 微笑 大笑 拽 大哭 亲亲 流汗 喷血 奸笑 囧 不爽 晕 示爱 害羞 吃惊 惊叹 爱你 吓死了 呵呵

评论信息框

这篇文章还没有收到评论,赶紧来抢沙发吧~