前言
首先大家看到我文章的标题,我这里所谓的“通读审计”就是把整个代码审计过程一字不差的记录下来。因为审计过很多CMS,也看过很多代码审计文章,大多数都是把漏洞点提一下然后文章结束,对于不懂MVC的审计者来说,看其他人代码审计文章,很多细节问题搞不明白(因为我自己也是从那里过来的),所以读起来不是太舒畅,自己学习起来也有点吃力,尤其到MVC框架一块。然后这把就是给大家分享一下我个人的整个代码审计过程,以及如何发现漏洞,如何自己构造语句等等。这次审计的cms是DOYOCMS建站系统,因为我们学校使用的该系统,就随便拿一套过来记录吧。也是给自己做个记录,给大家分享个人经验。
一、MVC的了解
这里就不再废话了,直接就开始吧。
Index.php第二行,上来直接require进来一个config.php,我们来看一下该文件到底做了哪些操作。
OK,config.php文件定义了一些常量以及数据库信息。
Index.php第三行。
$doyoConfig['view']['config']['template_dir'] = APP_PATH.'/template/'.$doyoConfig['ext']['view_themes'];
如下图的变量值赋给
$doyoConfig['view']['config']['template_dir']
Index.php的第四行的
require(DOYO_PATH."/sys.php");
把config.php的两个定义常量
define("APP_PATH",dirname(__FILE__));
define("DOYO_PATH",APP_PATH."/include");
的DOYO_PATH与sys.php拼接起来并且包含(包含include/sys.php)
我们看一下包含进来的sys.php文件进行了哪些操作
Sys.php的第四行包含进来一个Functions.php文件。从文件名上来看来猜测是包含来的一些方法吧。
打开看一下
好,确定包含方法。回到sys.php文件继续通读
Sys.php文件的第五行
$GLOBALS['G_DY'] = spConfigReady(require(DOYO_PATH."/inc.php"),$doyoConfig);
Require进来inc.php文件并且使用spConfigReady函数处理(之前提到的functions.php方法文件还记得吧)
跟进spConfigReady方法
可以看到第218行进行判断传入进来的$useconfig是否为数组,如果为数组进行遍历该数组,并且把传递过来的$useconfig保存到$preconfig内(合并数组)。
从他调用该方法中给形参1传递一个require进来的文件可以判断。该文件直接return一个数组。
打开包含进来的require(DOYO_PATH.”/inc.php”)文件来看一番
直接return一个数组
好,把inc.php文件return的数组与之前在index.php中定义过的$doyoConfig[‘view’][‘config’][‘template_dir’]保存到全局变量$GLOBALS[‘G_DY’]
回来继续读取sys.php
在functions文件中,跟进import方法。
再来看一下传递进来的值
39-43行的
if( TRUE == @is_readable($sfilename) ){
require($sfilename);
$GLOBALS['G_DY']['import_file'][md5($sfilename)] = TRUE;
return TRUE;
}
进行判断形参$sfilename是否可读,可读即包含。
OK,了解完import是用来读取文件之后,我们随着依次打开
这三个文件,看一下都有哪些操作。
哈哈,三个文件都是定义的类文件。
从字面上来看
syController:Controller的表面意思为控制器
syModel:Model的表面意思为模型
syView:View的表面意思为视图
MVC…
再回来读sys.php文件的第29-36行
判断分支给$GLOBALS[‘G_DY’][‘url’][“url_path_base”]进行赋值
再读取sys.php文件的第42-50行
看到!empty($_SERVER[‘PATH_INFO’],是用来实现伪静态化的操作的
下面的52-58行是作为重点。
判断分支给$GLOBALS[‘G_DY’][‘url’][“url_path_base”]进行赋值
再读取sys.php文件的第42-50行
看到!empty($_SERVER[‘PATH_INFO’],是用来实现伪静态化的操作的
下面的52-58行是作为重点。
跟进syClass与spLaunch方法
function syClass($class_name, $args = null, $sdir = null, $force_inst = FALSE){
if(preg_match("/^[a-zA-Z0-9_\-]*$/",$class_name)==0)syError("类定义不存在,请检查。");
if(TRUE != $force_inst)if(isset($GLOBALS['G_DY']["inst_class"][$class_name]))return $GLOBALS['G_DY']["inst_class"][$class_name];
if(null != $sdir && !import($sdir) && !import($sdir.'/'.$class_name.'.php'))return FALSE;
$has_define = FALSE;
if(class_exists($class_name, false) || interface_exists($class_name, false)){
$has_define = TRUE;
}else{
if( TRUE == import($class_name.'.php')){
$has_define = TRUE;
}
}
if(FALSE != $has_define){
$argString = '';$comma = '';
if(null != $args)for ($i = 0; $i < count($args); $i ++) { $argString .= $comma . "\$args[$i]"; $comma = ', ';}
eval("\$GLOBALS['G_DY']['inst_class'][\$class_name]= new \$class_name($argString);");
return $GLOBALS['G_DY']["inst_class"][$class_name];
}
syError($class_name."类定义不存在,请检查。");
}
二、 PHP任意文件替换漏洞
紧接着上面继续往下分析
我们打开sysession文件看一下。
自改了session存储机制。注意看write方法。写入session操作。
看到在file_put_contents写入前拼接了” <?php die();?>”关键字。首先我们想到php://filter进行绕过。但是我们可以看到前面拼接了一个$filedir。$filedir变量是路径信息。不可控。则php://filter无法绕过。
file_put_contents在windows下我们可以进行写入冒号文件流的格式。例如:xxx.php:.php。可以逃逸<?php exit;?>。但是生成后的文件却是空的。再进行xxx.php<<<写入文件数据,思路是这样的,但是。我们还可以看到在拼接$name的同时。后面连接了一个’.php’。那么我们提交的数据就变成了xxx.php<<<.php。显然。打乱了文件流规则。也无法利用!
我们没有任何机会来通过session文件来getshell
但是从另一个角度讲,file_put_contents函数是由fopen函数与fwrite函数封装起来的。使用了’w’的写入规则。
比如,这里有一个1.php。文件内容为123,当你file_put_contents(‘1.php’,’xxx’);的时候,1.php文件内容”123”会被替换为我们传入的”xxx”
再来看看程序自写的session存储机制居然没有对路径做任何过滤。
那么就比较有趣了。我们可以通过修改session的值。来进行替换任何文件。
就可以成功把根目录下的 /include/inc.php 文件内容给替换掉。include/inc.php 文件内容为网站配置。该框架中有运行。再结合之前程序写入session文件内容所定义的<?php die();?>。可以导致网站无法正常访问。(被die暂停程序运行)。
验证漏洞结果:
三、继续观察MVC
我们再回到functions.php文件进行读取第11行
$handle_controller = syClass($__controller, null, $GLOBALS[‘G_DY’][“controller_path”].’/’.$__controller.”.php”);
进行syClass函数处理。我们之前了解到syClass方法是用来处理类文件并包含的。
传入的第三个参数亮了。
根据我们传递的参数,则/source/$_GET[‘c’]是我们所包含的文件。我们在source文件夹下创建test.php进行验证一下
看来逻辑是没有问题的
接着我们继续往下看functions.php文件的第16行
这样来,我们整个的处理逻辑搞清楚开始挖掘漏洞
四、SSRF漏洞
当前我在functions.php这个文件。既来之,则安之。我通过危险自定义的一些方法来寻找漏洞
我从functions.php文件的第100-117行发现了
该函数传入的$url如果没有严谨的过滤,则会造成SSRF漏洞
全局搜索一下这个函数。
看到大名鼎鼎的a_sys类文件($_REQUEST[‘c’])与调用该漏洞的方法($_REQUEST[‘a’])
可是问题来了,我们所包含类文件的路径是 /source,而不是/source/admin/xxx.php
那么我们如何引入呢?不慌。
全局搜索一下哪里修改了$doyoConfig[‘view’][‘config’][‘template_dir’]的值(该值影响包含路径)
在admin.php中重现了index.php的操作。只是略有一些不同
那么我们SSRF漏洞利用点也只能在后台了,有一定的局限性
登陆后台后
我们包含a_sys.php文件并实例化该类,然后调用template_cache方法
这里是传入的$y。我们再该文件内翻半天也不会翻到syArgs方法的。
原因很简单,继承了一个类。
这里构造方法有了和template_cache方法相同的步骤
我们之前所看过的这个类的一个syArgs方法
可是在syController中也没有找到syArgs方法,那么我们就需要看一下__call魔术方法的处理了
第48行
return syClass($name)->__input($args);
syClass我们之前了解到的功能就是进行包含syArgs这个类文件或实例化该类,随后调用__input方法,把我们__construct的$this->a=$this->syArgs(‘a’,1);中’a’,1以数组的形式传递给__input方法
看一下get方法
调用了has方法,把字符串’a’传递进去了
跟进has方法
咦?多了一个成员属性$this->args
我们看一下构造方法是否初始化该属性
该类的构造方法
args成员属性是$_REQUEST进来的变量
如果我们传入的?a=template_cache,返回true
再回来看一下该分支结构
继续跟进filters方法
看到该方法是用来过滤传入数据内容的。在其中的 case 0,1,2,3,4,5,中,可以看到都对传入的数据进行过滤操作。可是在case 2中,漏洞出现了。这里涉及到array_walk_recursive函数利用问题。
看着arrays方法的代码块,好像过滤的还挺全的。不仔细想一想还真不知道。
array_walk_recursive函数并不会影响到数组本身。例如:
可以看到我们放入的 test=>’aaa,单引号并没转义。好了,现在了解到syArgs的第二个参数存在未过滤问题。
我们继续回到第102-105行的操作
再回到我们之前这一步
把我们传入的y进行实体化编码(htmlspecialchars)以及防注入(addslashes)操作,但是我们当前发现的漏洞输入SSRF类型的,所以对我们毫无影响~
好了,构造payload: admin.php?c=a_sys&a=template_cache&y=127.0.0.1:3306
验证漏洞结果:
五、SQL注入漏洞
那么我们知道syArgs(x,2)的第二个参数存在漏洞问题之后。我们全局搜索:,2)。来查找哪一地方使用了该函数。
可以看到update关键词。这东西对我们非常敏感。审计突然就像喝了红牛一样。精神杠杠的。
PS:这里if验证跟进就不再说了。直接看update。
果然,是自己封装的update语句。
那么构造
Payload:?c=a_classtypes&a=alledit&orders[]=1′ or updatexml(2,concat(0x7e,(version())),0) or’
验证漏洞结果:
*本文作者:Heihu577,转载请注明来自FreeBuf.COM
来源:freebuf.com 2020-04-11 08:00:02 by: Heihu577
请登录后发表评论
注册