最近研究源码审计相关知识,会抓起以前开源的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'])变化发生后,基本上可以确定漏洞的触发点位于此函数中。
2. 定位member_input->get()后续分析
跟函数位于/phpcms/modules/member/fields/member_input.class.php在文件中,我想重施我的旧技能。我在这种 *** 中插入了代码,但我发现插入桩后无法打印到页面上。我别无选择(原因是我希望你能给我一些建议)。我只能一行审查代码,先贴上代码,便于分析:
functionget($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($dataas$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)"<divclass="content_attr"><label><inputname="add_introduce"type="checkbox"value="1"checked>是否截取内容</label><inputtype="text"name="introcude_length"value="200"size="3">摘要中的字符<label><inputtype='checkbox'name='auto_thumb'value="1"checked>是否获得内容之一</label><inputtype="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"["i *** ase"]=>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行,单独截图如下⤵:
这里更重要的是找出来$func这个函数,找到此函数["formtype"]=>string(6) “editor”,可知$func就是editor()函数,editor函数传入的参数是上面列出的一长串字符串img下面将跟进标签的内容editor函数,真相似乎即将在世界上大白。
3. 跟进editor函数及后续函数
editor()函数位于/phpcms/modules/member/fields/editor/imput.inc.php在文件中,老规则先贴代码:
functioneditor($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($remotefileurlsas$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操作造成的。