代码审计直通车
2022-11-16 00:2:2 Author: 白帽子(查看原文) 阅读量:13 收藏

简介

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,待过滤的字符串或字符串数组,
$forcetrue,忽略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{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{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。

SQL注入


在后台有个碎片管理的删除功能。这个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余项,研究成果应用于产品核心技术研究、国家重点科技项目攻关、专业安全服务等。对安全感兴趣的小伙伴可以加入或关注我们。

我知道你在看


文章来源: http://mp.weixin.qq.com/s?__biz=MzAwMDQwNTE5MA==&mid=2650246419&idx=2&sn=2fbb53eefa45f313b11437d60b0c965e&chksm=82ea56bab59ddfacbb266c2dc513a018d438089d5deaa625c16835d2904382bd808cd8a668ce#rd
如有侵权请联系:admin#unsafe.sh