简介
YXcms是基于PHP+MySql开发,采用CANPHP框架编写的。 高效 采用三级缓存:数据库缓存、模板缓存、静态缓存,可使网站数据达到百万级负载! 灵活 采用功能与显示分离设计,灵活的标签库和任意拓展的插件机制,让您随心所欲,将DIY进行到底! 实用 拥有建站各种实用功能,摒弃各种复杂繁琐的功能操作。卓越的用户体验,让您使用起来方便明了! 免费 遵循BSD开源协议,不对用户做任何功能限制,保证用户二次商业开发使用!
官网地址:[http://www.ycparking.cn/](http://www.ycparking.cn/)
没有找到下载链接,可能是没开源,没有免费版。这是官网效果图。
于是到站长之家下了份源码。
下载完成在本地的环境运行安装了下。
前后台页面效果大致是这个样子
xss漏洞
这个地方,需要解释下,这个框架本身的代码做的过滤是很到位的在xss上,这里存在xss是为因为用的第三方的编辑器的问题,导致过滤上出现了问题。下面展示下效果,和存在的问题的代码分析。
在前台有个留言本的功能,这个提交完成会在后台的管理审核的时候,调用第三方的富文本编辑器kindediter,恰巧这个编辑把系统自带的过滤给干掉了。
模拟数据的提交。在留言内容的时候,我们填写payload:<svg onload="alert(1)">
从数据的流向,我们也顺带看下系统自带的过滤规则,根据表单的URL提交地址/index.php?r=default/column/index&col=guestbook,
我们找到代码方法体,
public function index()
{
$ename=in($_GET['col']);
if(empty($ename)) throw new Exception('栏目名不能为空~', 404);
$sortinfo=model('sort')->find("ename='{$ename}'",'id,name,ename,path,url,type,deep,method,tplist,keywords,description,extendid');
$path=$sortinfo['path'].','.$sortinfo['id'];
$deep=$sortinfo['deep']+1;
$this->col=$ename;
switch ($sortinfo['type']) {
case 1://文章
$this->newslist($sortinfo,$path,$deep);
break;
case 2://图集
$this->photolist($sortinfo,$path,$deep);
break;
case 3://单页
$this->page($sortinfo,$path,$deep);
break;
case 4://应用
break;
case 5://自定义
break;
case 6://表单
$this->extend($sortinfo,$path,$deep);
break;
default:
throw new Exception('未知的栏目类型~', 404);
break;
}
}
在方法里有个in的方法,这是接受了get参数,对值进行了处理。处理方法如下:
/*
功能:用来过滤字符串和字符串数组,防止被挂马和sql注入
参数$data,待过滤的字符串或字符串数组,
$force为true,忽略get_magic_quotes_gpc
*/
function in($data,$force=false){
if(is_string($data)){
$data=trim(htmlspecialchars($data));//防止被挂马,跨站攻击
if(($force==true)||(!get_magic_quotes_gpc())) {
$data = addslashes($data);//防止sql注入
}
return $data;
} else if(is_array($data)) {
foreach($data as $key=>$value){
$data[$key]=in($value,$force);
}
return $data;
} else {
return $data;
}
}
提示:
1、对HTML标签进行了实体转码
2、对特殊字符进行了转义
这是系统的第一次的过滤,接着往下走,到了case的分支,这里通过抓包,走的是6,也就是extend方法,在这个方法里面,根据我们提交包的方式post,以及,参数值是字符串的形式,在这个方法里,走的是下面的代码段:
if(strlen($_POST[$tableinfo[$i]['tableinfo']])>65535) $this->error('提交内容超过限制长度~');
$data[$tableinfo[$i]['tableinfo']]=html_in($_POST[$tableinfo[$i]['tableinfo']],true);
第一句是限制了用户输入参数值得长度不能是65535。
第二句是接受了post的值进行了html_in的过滤。下面看这个方法的声明。
//html代码输入
function html_in($str,$filter=false){
if($filter){
$str=RemoveXSS($str);
}
$str=htmlspecialchars($str);
if(!get_magic_quotes_gpc()) {
$str = addslashes($str);
}
return $str;
}
通过上面的传参,html_in的方法里,形参赋值为true,所以if判断成立,于是乎又调用了RemoveXSS的方法,调用了以后,进行了HTML标签实体转码,和特殊字符转义。那看看这个RemoveXSS里面做的哪些:
function RemoveXSS($val) {
// remove all non-printable characters. CR(0a) and LF(0b) and TAB(9) are allowed
// this prevents some character re-spacing such as <java\0script>
// note that you have to handle splits with \n, \r, and \t later since they *are* allowed in some inputs
$val = preg_replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '', $val); // straight replacements, the user should never need these since they're normal characters
// this prevents like <IMG [email protected]:alert('XSS')>
$search = 'abcdefghijklmnopqrstuvwxyz';
$search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$search .= '[email protected]#$%^&*()';
$search .= '~`";:?+/={}[]-_|\'\\';
for ($i = 0; $i < strlen($search); $i++) {
// ;? matches the ;, which is optional
// 0{0,7} matches any padded zeros, which are optional and go up to 8 chars // @ @ search for the hex values
$val = preg_replace('
/(&#[xX]0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $val); // with a ;
// @ @ 0{0,7} matches '0' zero to seven times
$val = preg_replace('/(�{0,8}'.ord($search[$i]).';?)/', $search[$i], $val); // with a ;
}
// now the only remaining whitespace attacks are \t, \n, and \r
$ra1 = Array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base');
$ra2 = Array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload');
$ra = array_merge($ra1, $ra2);
$found = true; // keep replacing as long as the previous round replaced something
while ($found == true) {
$val_before = $val;
for ($i = 0; $i < sizeof($ra); $i++) {
$pattern = '/';
for ($j = 0; $j < strlen($ra[$i]); $j++) {
if ($j > 0) {
$pattern .= '(';
$pattern .= '(&#[xX]0{0,8}([9ab]);)';
$pattern .= '|';
$pattern .= '|(�{0,8}([9|10|13]);)';
$pattern .= ')*';
}
$pattern .= $ra[$i][$j];
}
$pattern .= '/i';
$replacement = substr($ra[$i], 0, 2).'<x>'.substr($ra[$i], 2); // add in <> to nerf the tag
$val = preg_replace($pattern, $replacement, $val); // filter out the hex tags
if ($val_before == $val) {
// no replacements were made, so exit the loop
$found = false;
}
}
}
return $val;
}
这个函数,过滤掉不可打印的字符编码,通过定义的ra1的敏感字符标签,和ra2的javascript方法,通过替换替换查找,破坏了原有的on函数。如:onerror->on<x>error
保存数据库之前的数据包:
这样到后台查看到提交的结果:
现在看后台的内容字段,显示编辑页面用了编辑器,查看源码,它的onerror又把上面的过滤后的字符串好像是还原了。
这个直接看代码层面:
//单图删除,ajax方式使用
public function delpic()
{
if(empty($_POST['picname'])) $this->error('参数错误~');
$picname=$_POST['picname'];
$path=$this->uploadpath;
if(file_exists($path.$picname))
@unlink($path.$picname);
else{echo '图片不存在~';return;}
if(file_exists($path.'thumb_'.$picname))
@unlink($path.'thumb_'.$picname);
else {echo '缩略图不存在~';return;}
echo '原图以及缩略图删除成功~';
}
很明显这个方法直接接受了picname参数的值,没有做任何的过滤操作。我们可以使用回溯符号../../找到想要删除的文件,直接给干掉。
任意文件写入
后台存在这么个模块,叫前台模板,里面有个模板管理,下面是添加模板的具体方法,
public function tpadd()
{
$tpfile=$_GET['Mname'];
if(empty($tpfile)) $this->error('非法操作~');
$templepath=BASE_PATH . $this->tpath.$tpfile.'/';
///Applications/MAMP/htdocs/YXcmsApp1.4.7/protected/apps/default/view/default/
if($this->isPost()){
$filename=trim($_POST['filename']);
$code=stripcslashes($_POST['code']);
if(empty($filename)||empty($code)) $this->error('文件名和内容不能为空');
$filepath=$templepath.$filename.'.php';
if($this->ifillegal($filepath)) {$this->error('非法的文件路径~');exit;}
try{
file_put_contents($filepath, $code);
} catch(Exception $e) {
$this->error('模板文件创建失败!');
}
$this->success('模板文件创建成功!',url('set/tplist',array('Mname'=>$tpfile)));
}else{
$this->tpfile=$tpfile;
$this->display();
}
}
通过抓包,这个templepath路径:在本地的路径Applications/MAMP/htdocs/YXcmsApp1.4.7/protected/apps/default/view/default/
而内容只使用了stripcslashes函数,做了反斜杠的去除,过滤基本为零。轻轻松松写shall。
在后台有个碎片管理的删除功能。这个post包的传递参数delid是个数组,当在id上添加单引号的时候,抓包重放,回显有明显的注入错误信息
先上SQLmap跑一波,看看能不能跑出来
存在,分析代码层
public function del()
{
if(!$this->isPost()){
$id=intval($_GET['id']);
if(empty($id)) $this->error('您没有选择~');
if(model('fragment')->delete("id='$id'"))
echo 1;
else echo '删除失败~';
}else{
if(empty($_POST['delid'])) $this->error('您没有选择~');
$delid=implode(',',$_POST['delid']);
if(model('fragment')->delete('id in ('.$delid.')'))
$this->success('删除成功',url('fragment/index'));
}
}
从代码段里,程序直接接受了这个delid的字段,直接进行了in的方法拼接。在底层的delete方法里,初始化了where的条件,
//解析查询条件
public function parseCondition($options) {
$condition = "";
if(!empty($options['where'])) {
$condition = " WHERE ";
if(is_string($options['where'])) {
$condition .= $options['where'];
} else if(is_array($options['where'])) {
foreach($options['where'] as $key => $value) {
$condition .= " `$key` = " . $this->escape($value) . " AND ";
}
$condition = substr($condition, 0,-4);
} else {
$condition = "";
}
}
通过参数的拼接,这里if判断是不是一个字符串,如果是,直接赋值,包括数据里面的单引号也没有给过滤掉。所以执行SQL的时候,带入了单引号。
至此,整个cms的漏洞复现分析完结!
E
N
D
关
于
我
们
Tide安全团队正式成立于2019年1月,是新潮信息旗下以互联网攻防技术研究为目标的安全团队,团队致力于分享高质量原创文章、开源安全工具、交流安全技术,研究方向覆盖网络攻防、系统安全、Web安全、移动终端、安全开发、物联网/工控安全/AI安全等多个领域。
团队作为“省级等保关键技术实验室”先后与哈工大、齐鲁银行、聊城大学、交通学院等多个高校名企建立联合技术实验室,近三年来在网络安全技术方面开展研发项目60余项,获得各类自主知识产权30余项,省市级科技项目立项20余项,研究成果应用于产品核心技术研究、国家重点科技项目攻关、专业安全服务等。对安全感兴趣的小伙伴可以加入或关注我们。
我知道你在看哟