代码审计-天目CMS


前言

继续MVC的代码审计学习,这次审计的是天目MVC核心版.官方介绍:TEMMOKUMVC是邳州天目网络科技有限公司开发的一款专业的PHP+MYSQL产品,采用自主MVC构架,适用大型及中小型企业的开源MVC。本次审计出来的漏洞都出现于后台,危害有限这里只供学习参考。

了解网站的基本架构

我觉得在初期通读程序的底层代码了解它是怎么跑起来的对自身的代码审计的能力有很好的帮助。

开篇定义了一些常量包含了一个入口文件:

require 'temmoku'.DS.'run.php';

跟进查看./temmokou/run.php文件,前面的一堆没什么好看的,主要最后一句:

Temmoku\app::run();

跟进查看run静态方法,这基本整个框架的运行流程了:

我们一个方法一个方法的跟进查看,首先是spl_autoload_register函数,这个函数主要是用来自动包含文件用的,用来加载出实例化不到的类。如果加载实例化不到的类就会自动调用Load_Class方法。我们跟进查看发现是spl_autoload_register的常见用法包含类文件。

接着我们看看setReporting()静态方法:

主要是一些报错处理,这对于我们挖掘报错注入拥有帮助这里先不进行关注。

继续查看default_config()静态方法:

可以看到对方先进行高速缓存处理,下面还定义了一堆的常量,我们主要看看下面调用的Load_conf方法:

因为CACHING默认为空,所以这段的if判断不会进入我们先看一下下面的代码:

如果传进来的是一个文件夹那么它会对里面所有的文件执行Load_conf_mode方法操作,而传入单个文件也会执行Load_conf_mode方法操作,跟进Load_conf_mode方法进行查看发现是文件包含操作,如果是.conf后缀结尾的文件则进行反序列化操作。

做个小实验在app目录下添加一个test文件夹里面随便写一个文件代码为

<?php
phpinfo();

然后再``default_config()`方法中添加代码:

$test=APP_PATH.'/test';
Load_conf($test);

页面成功解析phpinfo的代码说明我们逻辑没有问题。

我们继续向下回到default_config()方法中向下通读,我们可以发现里面调用了多处C方法:
从代码注释中可以看到这里主要保存了一些程序变量,这些变量和常量一样,多且杂乱,对我们代码审计不太友好,在freebug学习一个大佬审计的时候发现一个很好的方法:

因为这里主要用$_config这个数组进行保存的所以我们只需要整一个形参,到程序最后调用就可以获取到这个大数组了。

在./temmoku/run.php下添加如下代码:

var_dump(C(NUll,null,null,1));

把该数组保存起来便于我们后面继续代码审计。

default_config()静态方法看完后我们继续向下看:

new route()

我们看看其初始化方法,先调用Load_conf()方法,前面已经分析过这个函数,放在这里通俗的讲就是包含module_route.php文件。

    function __construct()
    {            
        //加载模块配置信息
           $route=APP_PATH.'module_route.php';
        is_file($route) && Load_conf($route);
        define('NOW_TIME',      $_SERVER['REQUEST_TIME']);
        define('REQUEST_METHOD',$_SERVER['REQUEST_METHOD']);
        self::Route();
       }

看看module_route.php文件都是什么东西:

一些路由信息,OK继续回来通读:

看到最后调用的静态方法self::Route();该方法的代码非常长这里重点讲一下:

首先添加多一个访问路由的方法$_GET['temmoku_dirs'],说句老实话没看懂为什么要多添加这样的一个路由访问方式,在浏览网站的时候也没看出来其作用在那?如果懂开发的师傅可以告诉我一下

if($_GET['temmoku_dirs']){
  $_SERVER['PATH_INFO']=$_GET['temmoku_dirs'];
 }

第29-42行用来检查PATH_INFO,我们继续往下看,42-54行也是一些规范化PATH_INFO处理主要去除里面的空值。从刚才大数组我们可以看到ROUTE这个值默认为空,所以56到75行我们先不关注。

继续看下面77到86行你会发现又是PATH_INFO规范性处理,这次是把后缀给去掉和PATH_INFO前后的空格去掉。继续向下看

这里可以看到$_SERVER[PATH_INFO]的第一个传参就是模块了,第92行将$_SERVER[PATH_INFO]分隔为两个数组,第93行获取到第一个参数,第96-113检测网站是否安装的同时对模块进行验证,判断传入的模块$_config数组中MODULE_RUOTE是否存在,存在则定义MODULE常量为$test_module。

如果$_SERVER[PATH_INFO]不存在的时候,检测是否安装程序,把MODULE常量设置为C('index')变量

继续向下看因为URL_RULES默认为空我们继续向下看,从箭头处可以看出来对于admin路由模块这里还定义了新的路由方式,我们先不管稍后在进行分析。

在159到177行我们可以看到剩下控制器和方法的设置方法。并且在最后处我们可以看到其默认设置为index。

继续看看Admin_Route()方法:

前面一段都是在定义各种常量没什么好看,主要下面这一段:

这里作者再次对PATH_INFO里面的参数进行了规范性出来使用_来进行参数并对一些字符进行了替换。这里主要的影响在于我们不能向平时一样的使用GET,POST参数,传参的时候需要这样进行:http://xx/xxx_233

别因为这里看完了,在该文件的最后面还有一个魔法函数:

    public function __destruct() {
       self::filtering();
       self::un_reg_ister_Globals();
    }

更进查看发现对原生的GET,POST等方法进行了过滤:

代码如下:

function filtering(&$content){
    if(is_array($content)){
        foreach($content as $key=>$value){
            $array=['id','aid','cid','uid','mid','cmid','iid','nid','cityid','proviceid','countyid','townid','upcid','state','reply_id','lid'];
            if(in_array($key,$array) && $key ){
                $content[$key]=intval($value);
            }elseif($key=='iddb'){
                foreach($value as $_key=>$_id){
                    $_id=intval($_id);
                    if($_id){
                         $content[$key][$_key]=$_id;
                    }
                }
            }
        }
    }

    $content = is_array($content) ? array_map('filtering', $content) : htmlspecialchars($content,ENTi_QUOTES);
    return $content;
}

如果在该黑名单里面的键值数据都使用intval函数进行处理,其他的所有的数据都进行htmlspecialchars函数进行处理

从上面代码我们可以看出来该其存在以下几个问题:

  • 采用黑名单,但是所有的变量的值都会经过htmlspecialchars函数的处理,并且开启了ENTi_QUOTES设置单引号无法绕过。
  • 没有对数组的KEY值进行任何的过滤。

这里我们脑海中可以构思出存在注入的几种情景:

  • 因为htmlspecialchars不过滤\所有当sql语句和下面类似的时候可能存在注入,这里我就不说为什么请读者自已思考。当然前提是该可控点不在黑名单里面。

SELECT * FROM XXX WHERE A=’可控’ AND B=’可控’

  • 不存在黑名单里面并且不被引号保护的sql语句
  • sql语句使用了数组的KEY值进行拼接。

到这里就把new route();看完成了,继续向下看:

```self::log();``

默认日志不开启,并且开启了也不能直接利用暂时忽略,继续向下看:

self::Load_Controller();

很简单,就是把前面获取到路由信息进行路径拼接然后调用相对应的控制器中的方法。

到这里我们整个框架基本运作流程已经明确了,写到这里都已经写下两千多字了~~。

底层DB类分析

一开始看的时候发现其使用了PDO的预处理方法,但是认真分析才发现这里其本质还是使用了字符拼接的功能:

例如这样的代码是不存在注入的:

$sql = "select * from users where user = ? and user_id= ?;
$stmt = $pdo->prepare($sql);
$stmt->bindParam(1,$username);
$stmt->execute();

因为PDO会对?(占位符)里面数据进行处理进行预处理,但是如果我们把用户的可控数据直接凭借例如:

$sql = "select * from users where user = ? and user_id= ".$_GET['id'];
$stmt = $pdo->prepare($sql);
$stmt->bindParam(1,$username);
$stmt->execute();

这样PDO的预处理技术就不会到$_GET[‘id’]进行任何处理,只要该参数没有过滤那必要存在过滤。

sql注入漏洞

经过上面漫长的分析我们开始找符合条件的sql注入漏洞。怎么找?笔者也只是采用全局搜索一个一个$_GET,$_POST分析。经过漫长的搜索终于让我找到了一个后台的sql注入。

注入点代码位置:./app/plugin/wechat/admin/model/index.php

可以看到这里使用把$_POST[‘wechat’]参数的$key值直接拼接到sql语句中导致了sql注入。

漏洞演示

登录后台,默认密码为admin,admin

选择插件系统->微信插件->参数设置

设置参数随便,然后提交抓包。把里面的参数的Key值修改成我们的注入语句即可。效果如下图。

任意文件删除-可直接删除整个站点

./temmoku/functions.php下的delete_dir方法如下。虽然采用了黑名单禁止了一些目录防止误删,但是这里使用in_array函数,该函数采用严格匹配只要我们在路径采用..这样进行目录穿越就可以绕过这个限制。

function delete_dir($dir){

    //判断目录是不是违法的,必须锁定下,防止误删。
    $not_del=[APP_PATH,Temmoku_PATH,Temmoku,TEMPLATE,TemmokuLib,APP_PATH.'plugin',APP_PATH.'plugin'.DS,TEMPLATE."admin".DS,TEMPLATE."admin",TEMPLATE."user".DS,TEMPLATE."user",TEMPLATE."home".DS,TEMPLATE."home"];

    if(!$dir || in_array($dir ,$not_del)){
        return;
    }

    if(is_dir($dir) || is_file($dir)){
        $path=$dir;
    }else{
        $path=Temmoku_PATH.$dir;
    }
    if(IS_WIN=='0' && function_exists('chomd')){
        chomd($path,'0777');
    }

    //判断是否是文件如果是文件,直接删除不需要遍历
    if(is_file($path)){
        @unlink ($path);
    }else{
        if ( $handle  =  @opendir ( $path )) {
            while (( $file  =  readdir ( $handle ))!== false) {
                if($file==='.' || $file==='..'){
                     continue;
                }
                delete_dir($dir."/".$file);
            }
            //关闭打开的目录
            closedir ( $handle );
            rmdir ( $path );
        }
    }
}

漏洞演示

./app/admin/controller/modular.php中del方法调用了该方法而且参数可控。

payload:

http:xxx/admin/modular/del/step_install/dir_...

成功删除整站:

后言

菜哭了没挖到价值更高的洞~~


文章作者: EASY
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 EASY !
 上一篇
yzmcms5.4后台Getshell分析 yzmcms5.4后台Getshell分析
前言看了先知YzmCMS 5.4 后台getshell的文章分析感觉该漏洞颇具学习价值,所以自已下载下来审计一波,学习学习其中的思路。 缓存文件Getshell我们看看缓存文件的写入函数_fileputcontents,文件位于yzmphp
2020-11-22 EASY
下一篇 
PHP中双引号的安全问题 PHP中双引号的安全问题
前言在PHP语言中,单引号和双引号都可以表示一个字符串,但是对于双引号来说,可能会对引号内的内容进行二次解释,这就可能会出现安全问题。 双引号引起的命令执行漏洞在双引号中倘若有${}出现,那么{}内的内容将被当做代码块来执行。例如以下代码可
2020-11-15 EASY
  目录