glFusion CMS V1.7.9 代码审计

三条CNVD记录

  • glFusion CMS v1.7.9存在任意用户注册漏洞,攻击者可利用该漏洞使用任何用户的邮箱进行注册。
  • glFusion CMS v1.7.9存在授权问题漏洞,攻击者可利用该漏洞通过/public_html/users.php进行攻击。
  • glFusion CMS v1.7.9存在访问控制错误漏洞,攻击者可利用该漏洞通过/public_html/comment.php进行攻击。

CMS

特点

  1. 支持很多类型
  2. 扩展性强,已安装几个插件
  3. 安全:最近,HP Fortify 执行了完整的源代码/Web 应用程序审计
  4. 开发人员友好:glFusion 有一个文档齐全的API,允许您通过现有的钩子和 API 开发插件、自动标记和其他自定义

目录结构

  • private

    • backups
    • data
    • language
    • lib
    • logs
    • plugins
    • sql
    • system
    • vendor
    • composer.json.PHPCLASSES
    • db-config.php.dist
    • emailglfusionstories
  • public_html

    • admin

    • backend

    • bad_behavior2

    • calendar

    • captcha

    • ckeditor

    • docs

    • filemgmt

    • filemgmt_data

    • forum

    • help

    • images

    • javascript

    • layout

    • links

    • mediagallery

    • polls

    • staticpages

    • 404.php

    • article.php

    • comment.php

    • css.php

  • LICENSE
  • VERSION

/admin/index.php

登录访问/admin/index.php,Xdebug监听访问请求进行调试。

auth.inc.php

先不看 lib-common.php ,先看看认证文件 auth.inc.php 。

require_once '../lib-common.php';
require_once 'auth.inc.php';

F7进入 auth.inc.php
检查常量GVERSION,判断该文件能否独立被访问。
调用函数 USES_lib_user(); 进行权限认证。
F7进入USES_lib_user();,跳转到lib-common.php

function USES_lib_user() {
global $_CONF;
require_once $_CONF['path_system'] . 'lib-user.php';
}

F7 进入 lib-user.php ,先看这个文件的用户鉴权。

用户鉴权 lib-user.php

// 1.删除用户账号
function USER_deleteAccount($uid){}
// 2.创建一个新密码并发送邮件给用户?
function USER_createAndSendPassword($username, $useremail, $uid, $passwd = ''){}
// 3.为用户创建uid
function USER_createActivationToken($uid,$username){}
// 4.发送邮件通知用户已激活
function USER_sendActivationEmail ($username, $useremail){}
// 5.创建一个新的用户账号
function USER_createAccount ($username, $email, $passwd = '', $fullname = '',$homepage = '', $remoteusername = '', $service = '', $ignore = 0)
// 6.用户注册时发送邮件
function USER_sendNotification ($username, $email, $uid, $mode='inactive')
// 7.获取用户上传或外部引用的照片
function USER_getPhoto ($uid = 0, $photo = '', $email = '', $width = 0, $fullURL
= 1)
// 8.删除用户照片
function USER_deletePhoto ($photo, $abortonerror = true)
// 9.把用户添加到用户组
function USER_addGroup ($groupid, $uid = '')
// 10.从用户组中删除用户
function USER_delGroup ($groupid, $uid = '')
// 11.检查邮箱地址
function USER_emailMatches ($email, $domain_list)
// 12.确保用户名不重复
function USER_uniqueUsername($username)
// 13.检查用户名中是否包含无效字符
function USER_validateUsername($username, $existing_user = 0)
// 14.去除name中不允许的字符
function USER_sanitizeName($text)
// 15.获取子用户组
function USER_getChildGroups($groupid)
// 16.生成一个密码
function USER_createPassword ($length = 7)
// 17.建立用户有权限访问的topic的清单
function USER_buildTopicList ()
// 18.xxx
function USER_mergeAccountScreen( $remoteUID, $localUID, $msg='' )
// 19.Un-Merge User Accounts
function USER_unmergeAccounts()
// 20.Merge User Accounts
function USER_mergeAccounts()
uid(先看uid再说用户权限模型)

276行是生成token的代码。token通过加密,且加密方式是哈希、加密内容包含随机数。token的作用是cookie。

uid是什么时候生成的?应该是用户注册时。查看注册功能点,位于文件/user.php?mode=new

function USER_createActivationToken($uid,$username)
{
global $_CONF, $_TABLES;
$token = md5($uid.$username.uniqid (mt_rand (), 1));
DB_query("UPDATE {$_TABLES['users']} SET
act_token='".DB_escapeString($token)."', act_time=NOW() WHERE uid=".$uid);
return $token;
}

filemgmt插件文件上传

上传点URL:http://127.0.0.1/glfusion-1.7.9/public_html/admin/plugins/filemgmt/index.php?lid=1&op=modDownload

查看 /admin/plugins/filemgmt/index.php 文件(该部分分析的主要文件),全文搜索 jpeg 。

不太容易找到关键代码,开Xdebug进行调试。

定位执行函数
// 34行,通用配置和鉴权代码
require_once '../../../lib-common.php';
require_once '../../auth.inc.php';
USES_lib_admin();
。。。
// 118行,创建了三个类,插件配置代码
// 第三个是错误处理类,用于处理异常情况
// 第二个使用 __construct()初始化赋值了数据库的数据库名、表名、id名、pid名
$myts = new MyTextSanitizer;
$mytree = new XoopsTree($_DB_name,$_TABLES['filemgmt_cat'],"cid","pid");
$eh = new ErrorHandler;

接收参数之后,使用 SEC_hasRights() 函数进行校验,因为不走该流程所以暂时忽略。

// 44行,对参数使用过滤器后赋值
$op = isset($_REQUEST['op']) ? COM_applyFilter($_REQUEST['op']) : '';

文件有效执行:switch语句选择 option

从一百多行开始,一直都是定义的 function() 代码,直到1493行,对操作参数使用 switch 语句。

这些操作参数,就是该插件的操作。

文件上传操作,传递的操作参数是 op=modDownloadS ,接下来跟进该函数。

switch ($op) {
default:
mydownloads();
break;
case "comment":
filemgmt_comments($firstcomment);
break;
case "delNewDownload":
delNewDownload();
break;
case "addCat":
addCat();
break;
case "addSubCat":
addSubCat();
break;
case "addDownload":
addDownload();
break;
case "listBrokenDownloads":
listBrokenDownloads();
break;
case "delBrokenDownloads":
delBrokenDownloads();
break;
case "ignoreBrokenDownloads":
ignoreBrokenDownloads();
break;
case "approve":
approve();
break;
case "delVote":
delVote();
modDownload();
break;
case "delCat":
delCat();
break;
case "modCat":
modCat();
break;
case "modCatS":
modCatS();
break;
case "modDownload":
modDownload();
break;
case "modDownloadS":
modDownloadS();
break;
case "delDownload":
delDownload();
break;
case "categoryConfigAdmin":
categoryConfigAdmin();
break;
case "newfileConfigAdmin":
newfileConfigAdmin();
break;
case "listNewDownloads":
listNewDownloads();
break;

操作函数 modDownloads()

F7进入函数,跳转到该文件 744 行,下断点。
该函数末尾代码是 exit() ,说明该函数是最后执行的函数,代码数 130+ 。接下来分析该操作。

上传操作函数 modDownloads()

文件744行开始,该函数代码有130行,首先对不关键的代码进行文字说明,着重分析关键代码。

该函数有两段上传处理代码,第一段在 761-816 行,处理上传文件变量 $newfile

第二段是 817-881行,处理上传文件变量 newsnapfile

两段上传代码都使用 /private/system/classes/upload.class.php 的 upload 类。

function modDownloadS() {
// 对传递参数使用 COM_applyFilter()函数处理后进行赋值
加载全局变量
开启demo_mode则退出程序
// 参数接收
接收参数cid
url参数:如果原图片文件存在,则对url代表的原文件执行操作
过滤后接收参数silentedit
过滤后接收参数owner_id
lid参数:*
// 获取文件
获取文件名:使用 MyTextSanitizer->makeTboxData4Save()处理新文件的文件名
// 创建上传类的实例,对上传文件进行处理。765-810行代码
$upload->setFieldName('newfile');
$upload->setPath($filemgmt_FileStore);
$upload->setAllowAnyMimeType(true); // allow any file type
$upload->setMaxFileSize(100000000);
$upload->setMaxDimensions(8192,8192);
$upload->uploadFiles();

系统内置上传类 /private/system/classes/upload.class.php

upload类代码后,上传的文件是 phpinfo.php。

image.png

index.php,784行,又一层校验,把后缀php修改成phps.

$pos = strrpos($newfile,'.') + 1;
$fileExtension = strtolower(substr($newfile, $pos));

795行,复制 phpinfo.php 生成 phpinfo.phps。

801行,删除 phpinfo.php,此时文件夹只剩下 phpinfo.phps 文件,文件上传漏洞。

filemgmt\index.php操作数

默认情况:mydownloads()

switch语句如下:

default:
mydownloads();
break;

检索mydownloads,定位到函数位置122行。接下来走一下代码流程。

function mydownloads() {
//全局变量
//接收目录参数并过滤
//设置展示参数
// 从数据库查询目录列表,是写死的语句
$sql = "SELECT * FROM {$_TABLES['filemgmt_cat']} WHERE pid=0 ORDER BY title
ASC";
展示各个目录
}
第三种情况:delNewDownload()

检索 delNewDownload(,定位到964行。接下来走一下代码流程。

function delNewDownload() {
定义全局变量
// 接收lid参数并过滤处理
$lid = (int) COM_applyFilter($_POST['lid'],true);
// 传入表名、变量名、变量值。如果函数执行结果,弱比较等于1
if (DB_count($_TABLES['filemgmt_filedetail'],'lid',$lid) == 1) {
// 设置临时名、临时文件名、临时主机名。可能存在SQL注入,查看函数DB_getItem()
$tmpnames =
explode(";",DB_getItem($_TABLES['filemgmt_filedetail'],'platform',"lid=$lid"));
// delete语句,大概率存在SQL注入。
// 看两个函数,过滤函数COM_applyFilter(),查询函数DB_query()
DB_query("DELETE FROM {$_TABLES['filemgmt_filedetail']} WHERE
lid=$lid");
DB_query("DELETE FROM {$_TABLES['filemgmt_filedesc']} WHERE lid=$lid");
//如果临时文件名不为空、文件存在、非目录,则删除filemgmt_filedetail表格记录的文件。
//(任意文件删除应该无了)
//重定向index.php?op=listNewDownloads
//退出程序
}
情况:approve()

switch语句如下:

case "approve":
approve();
break;

检索 approve(,定位到1357行。接下来走一下代码流程。

function approve(){
//定义全局变量
//接收lib参数并做过滤处理
//接收参数title、cid、homepage、version、description
//接收size参数并做过滤处理
// 如果指定URL,则调用 $myts->makeTboxData4Save()函数,再使用rawurlencode()函数处理
// 看一个类、两个函数
if (($_POST['url']) || ($_POST['url'] != '')) {
$name = $myts->makeTboxData4Save($_POST['url']);
$url = rawurlencode($name);
}
//对POST参数做相似处理:logourl
// 看SQL语句,妥妥的SQL注入
// 看函数DB_query()
$result = DB_query("SELECT COUNT(*) FROM {$_TABLES['filemgmt_filedetail']}
WHERE url='$url' and status=1");
list($numrows) = DB_fetchArray($result);
//如果filemgmt_filedetail表存在该URL记录,则报错1108
//对参数纷纷使用$myts->makeTboxData4Save()函数进行保存
// 根据lid查询filemgmt_filedetail表,处理文件可能已经存在的情况
$tmpnames =
explode(";",DB_getItem($_TABLES['filemgmt_filedetail'],'platform',"lid='$lid'"))
;
//如果文件已存在且不是目录,则执行 @rename() 和 @chmod() 函数。