IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Phpcms v9漏洞分析

    nightmarelee发表于 2017-04-17 02:13:14
    love 0

    最近研究源码审计相关知识,会抓起以前开源的CMS漏洞进行研究,昨天偶然看见了这个PHPCMS的漏洞,就准备分析研究一番,最开始本来想直接从源头对代码进行静态分析,但是发现本身对PHPCMS架构不是很熟悉,导致很难定位代码的位置,最后就采用动态调试&静态分析的方式对漏洞的触发进行分析,下面进入正题。

    1、漏洞触发代码定位

    通过漏洞的POC(/phpcms/index.php?m=member&c=index&a=register&siteid=1 )判断,漏洞触发点的入口位于/phpcms/modules/member/index.php文件中的register()方法中,在代码中插入一些echo函数,观察输出(见下)的变化。从下面的结果变化可知,img标签的src属性是在执行完下面的get()函数:

    $user_model_info = $member_input->get($_POST['info'])

    后发生变化,因此基本可以确定,漏洞的触发点就是位于这个函数中。

    屏幕快照 2017-04-12 下午2.59.37.png

    屏幕快照 2017-04-12 下午3.02.04.png

    2、定位member_input->get()跟进分析

    跟进该函数,该函数位于/phpcms/modules/member/fields/member_input.class.php文件中,此处本来还想故技重施,在该方法中对代码进行插桩,但是发现插桩后的居然无法打印到页面上,没辙(原因望各位大神指点一二),只能对代码进行一行行推敲,先把代码贴上,方便分析:

                function get($data) {
    		$this->data = $data = trim_script($data);
    		$model_cache = getcache('member_model', 'commons');
    		$this->db->table_name = $this->db_pre.$model_cache[$this->modelid]['tablename'];
    		$info = array();
    		$debar_filed = array('catid','title','style','thumb','status','islink','description');
    		if(is_array($data)) {
    			foreach($data as $field=>$value) {
    				if($data['islink']==1 && !in_array($field,$debar_filed)) continue;
    				$field = safe_replace($field);
    				$name = $this->fields[$field]['name'];
    				$minlength = $this->fields[$field]['minlength'];
    				$maxlength = $this->fields[$field]['maxlength'];
    				$pattern = $this->fields[$field]['pattern'];
    				$errortips = $this->fields[$field]['errortips'];
    				if(empty($errortips)) $errortips = "$name 不符合要求!";
    				$length = empty($value) ? 0 : strlen($value);
    				if($minlength && $length < $minlength && !$isimport) showmessage("$name 不得少于 $minlength 个字符!");
    				if (!array_key_exists($field, $this->fields)) showmessage('模型中不存在'.$field.'字段');
    				if($maxlength && $length > $maxlength && !$isimport) {
    					showmessage("$name 不得超过 $maxlength 个字符!");
    				} else {
    					str_cut($value, $maxlength);
    				}
    				if($pattern && $length && !preg_match($pattern, $value) && !$isimport) showmessage($errortips);
    	                        if($this->fields[$field]['isunique'] && $this->db->get_one(array($field=>$value),$field) && ROUTE_A != 'edit') showmessage("$name 的值不得重复!");
    				$func = $this->fields[$field]['formtype'];
    				if(method_exists($this, $func)) $value = $this->$func($field, $value);
    	
    				$info[$field] = $value;
    			}
    		}
    		return $info;
    	}

    代码整体比较容易,可能比较难理解的就是$this->fields这个参数,这个参数是初始化类member_input是插入的,这个参数分析起来比较繁琐,主要是对PHPCMS架构不熟,那就在此走点捷径吧,在1中,直接将初始化完成后的member_input类dump出来,效果还不错,所有的参数都dump到页面上了,下面主要摘取比较重要的$this->fields[$field],即:【$this->fields["content"]】这个参数,如下所示⤵️:

    ["content"]=>
        array(35) {
          ["fieldid"]=>
          string(2) "90"
          ["modelid"]=>
          string(2) "11"
          ["siteid"]=>
          string(1) "1"
          ["field"]=>
          string(7) "content"
          ["name"]=>
          string(6) "内容"
          ["tips"]=>
          string(407) "<div class="content_attr"><label><input name="add_introduce" type="checkbox"  value="1" checked>是否截取内容</label><input type="text" name="introcude_length" value="200" size="3">字符至内容摘要
    <label><input type='checkbox' name='auto_thumb' value="1" checked>是否获取内容第</label><input type="text" name="auto_thumb_no" value="1" size="2" class="">张图片作为标题图片
    </div>"
          ["css"]=>
          string(0) ""
          ["minlength"]=>
          string(1) "0"
          ["maxlength"]=>
          string(6) "999999"
          ["pattern"]=>
          string(0) ""
          ["errortips"]=>
          string(18) "内容不能为空"
          ["formtype"]=>
          string(6) "editor"
          ["setting"]=>
          string(199) "array (
      'toolbar' => 'full',
      'defaultvalue' => '',
      'enablekeylink' => '1',
      'replacenum' => '2',
      'link_mode' => '0',
      'enablesaveimage' => '1',
      'height' => '',
      'disabled_page' => '0',
    )"
          ["formattribute"]=>
          string(0) ""
          ["unsetgroupids"]=>
          string(0) ""
          ["unsetroleids"]=>
          string(0) ""
          ["iscore"]=>
          string(1) "0"
          ["issystem"]=>
          string(1) "0"
          ["isunique"]=>
          string(1) "0"
          ["isbase"]=>
          string(1) "1"
          ["issearch"]=>
          string(1) "0"
          ["isadd"]=>
          string(1) "1"
          ["isfulltext"]=>
          string(1) "1"
          ["isposition"]=>
          string(1) "0"
          ["listorder"]=>
          string(2) "13"
          ["disabled"]=>
          string(1) "0"
          ["isomnipotent"]=>
          string(1) "0"
          ["toolbar"]=>
          string(4) "full"
          ["defaultvalue"]=>
          string(0) ""
          ["enablekeylink"]=>
          string(1) "1"
          ["replacenum"]=>
          string(1) "2"
          ["link_mode"]=>
          string(1) "0"
          ["enablesaveimage"]=>
          string(1) "1"
          ["height"]=>
          string(0) ""
          ["disabled_page"]=>
          string(1) "0"
        }

    有了上面的参数列表后,理解get()函数的代码就要轻松许多了,分析过程略。结论就是,漏洞的触发函数在倒数6、7两行,单独截个图,如下⤵️:

    屏幕快照 2017-04-12 下午3.28.38.png

    这里比较重要的是要找出$func这个函数,查查上面的表,找到["formtype"]=>string(6) “editor”,可知$func就是editor()函数,editor函数传入的参数就是上面列出的一长串字符串,和img标签的内容,下面将跟进editor函数,真相好像马上就要大白于天下了。

    3、跟进editor函数及后续函数

    editor()函数位于/phpcms/modules/member/fields/editor/imput.inc.php文件中,老规矩,先贴出代码:

    	function editor($field, $value) {
    		$setting = string2array($this->fields[$field]['setting']);
    		$enablesaveimage = $setting['enablesaveimage'];
    		if(isset($_POST['spider_img'])) $enablesaveimage = 0;
    		if($enablesaveimage) {
    			$site_setting = string2array($this->site_config['setting']);
    			$watermark_enable = intval($site_setting['watermark_enable']);
    			$value = $this->attachment->download('content', $value, $watermark_enable);
    		}
    		return $value;
    	}

    简单阅读代码,发现实际的触发流程发生在$this->attachment->download()函数中,直接跟进这个函数,这个函数位于/phpcms/libs/classes/attachment.class.php中download()函数,源代码有点长,就贴一些关键代码,⤵️

                    $string = new_stripslashes($value);
    		if(!preg_match_all("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i", $string, $matches)) return $value;
    		$remotefileurls = array();
    		foreach($matches[3] as $matche)
    		{
    			if(strpos($matche, '://') === false) continue;
    			dir_create($uploaddir);
    			$remotefileurls[$matche] = $this->fillurl($matche, $absurl, $basehref);
    		}
    		unset($matches, $string);
    		$remotefileurls = array_unique($remotefileurls);
    		$oldpath = $newpath = array();
    		foreach($remotefileurls as $k=>$file) {
                            if(strpos($file, '://') === false || strpos($file, $upload_url) !== false) continue;
    			$filename = fileext($file);
    			$file_name = basename($file);
    			$filename = $this->getname($filename);
    
    			$newfile = $uploaddir.$filename;
    			$upload_func = $this->upload_func;
    			if($upload_func($file, $newfile)) {

    代码主要是进行一些正则过滤等等操作,这里真正关键的是代码的最后一行的操作$upload_func($file, $newfile),其中$this->upload_func = ‘copy’;,写的在明白点就是copy($file, $newfile),漏洞就是一个copy操作造成的。

    *本文作者:nightmarelee,转载请注明来自Freebuf.COM



沪ICP备19023445号-2号
友情链接