当前位置: 首页 > 科技观察

Phpcmsv9漏洞分析

时间:2023-03-12 00:15:49 科技观察

最近在研究源码审计相关知识,抓取之前开源的CMS漏洞进行研究。昨天偶然看到这个PHPCMS的漏洞,于是打算分析研究一下。一开始想直接从源码上查代码。进行了静态分析,但是发现自己对PHPCMS的架构不是很熟悉,导致很难定位到代码的位置。最后通过动态调试&静态分析来分析漏洞的触发。让我们进入主题。1.漏洞触发代码位置由漏洞的POC(/phpcms/index.php?m=member&c=index&a=register&siteid=1)判断,漏洞触发点的入口位于register中/phpcms/modules/member/index.php文件中的()方法中,在代码中插入一些echo函数并观察输出(见下文)的变化。从下面结果的变化我们可以看出img标签的src属性在执行了如下get()函数后发生了变化:$user_model_info=$member_input->get($_POST['info']),所以是基本可以确定漏洞的触发点就在这个函数中。2.找到member_input->get()后续分析和后续这个函数,位于/phpcms/modules/member/fields/member_input.class.php文件中。我想在这里重复老把戏。在这个方法中插入代码,但是发现插入后无法打印到页面上,没办法(原因还望各位指点),只能逐行细看代码,先贴上代码方便分析:->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($值);如果($minlength&&$length<$minlength&&!$isimport)showmessage("$name不能小于$minlength个字符!");if(!array_key_exists($field,$this->fields))showmessage('不存在于model'.$field.'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,初始化后直接dumpmember_input类,效果还不错,所有的参数都dump到页面了,下面主要摘取比较重要的$this->fields[$fieldd],即:??[$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)"Content"["tips"]=>string(407)"CharactertoContentSummary图片作为标题图片

"["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_pa??ge'=>'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_pa??ge"]=>string(1)"0"}有了上面的参数列表,就更容易理解get()函数的代码,分析流程简单总结一下,漏洞的触发函数在最后6、7行,单独截图如下?:这里比较重要的是找出$func函数,查看上面table,找到["formtype"]=>string(6)"editor",可以看出$func就是editor()函数,editor函数传入的参数就是上面列出的一长串字符串以及img标签的内容。下面会跟进编辑器功能,真相似乎很快就要大白于天下了。3、跟进编辑器功能及后续功能。editor()函数位于/phpcms/modules/member/fields/editor/imput.inc.php文件中。和往常一样,先贴出代码:(isset($_POST['spider_img']))$enablesaveimage=0;如果($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))返回$值;$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),漏洞是由复制操作引起的