审计SEMCMSv2.7之捡来的两个洞加漏洞复现

前言

SEMCMS php v2.7 审计之前,我会去看看要审计的CMS官网是否存在手册说明什么的,然后去会各个漏洞公布平台找找它以前的老漏洞,复现下是否修复及修复是否完整(主要是去看看会不会有现金奖励)。

看着看着就发现了两处问题,算是来的漏洞吧。

防sql正则校验不合理

去官网看了下并没有什么文档,但搜到了其在 2.1~2.3 的版本中存在后台任意用户登陆的问题,随后就去看了看。

原因是后台登陆时会最终调用 xxxx_Admin/Include/function.php -> checkuser() 方法来验证账号:

function checkuser(){ //判断账号 
    $cookieuser=@htmlspecialchars($_COOKIE["scuser"]);
    $cookieuserqx=@htmlspecialchars($_COOKIE["scuserqx"]);
    $sql="select * from sc_user where user_ps='$cookieuser' and user_qx='$cookieuserqx'";
    $result=mysql_query($sql); 
    $row = mysql_fetch_array($result,MYSQL_ASSOC); 
    if (!mysql_num_rows($result)){ echo "<script language='javascript'>alert('账号密码不正确重新登陆!');top.location.href='index.html';</script>";} 
    else {echo'';}

}

按照文章中大佬说的方法,设置 Cookie 参数:scuser=\; scuserqx= or 1=1 # 及可实现任意用户登陆(其原理是用 \ 符号转义 $cookieuser 后面的单引号闭合,再通过 $cookieuserqx 参数完成注入语句)。

select * from sc_user where user_ps='\' and user_qx=' or 1=1 #'

但在 2.3 版本以后 web_inc.php 文件中引入了一个 web_sql.php 的文件,用于验证全局的 $_GET$_COOKIE 参数中是否存在注入语句:

<?php
// 防sql入注
if (isset($_GET)){$GetArray=$_GET;}else{$GetArray='';} //get
if (isset($_COOKIE)){$CookArray=$_COOKIE;}else{$CookArray='';} //cookie

foreach ($GetArray as $value) {//get
    verify_str($value);
}

foreach ($CookArray as $value) { //cookie
    verify_str($value);
}

function inject_check_sql($sql_str)
{
    return preg_match('/select|insert|=|<|update|\'|\/\*|\*|union|into|load_file|outfile/i', $sql_str);
}

function verify_str($str)
{
    if (inject_check_sql($str)) {
        exit('Sorry,You do this is wrong! (.-.)');
    }
    return $str;
}

这里我们主要看那段正则就可以了,上面用到的 or 1=1 # 已经不行了,因为里面含有等号。

这里其实很好绕过它的检测,我们只需要稍微改动下,依然用 or 就可以再次实现任务用户登录:or -1 #

带着伪造好的 cookie 参数,直接访问后台的 SEMCMS_Main.php 地址就可以登录进后台了。

至于后台怎么找,按照生成规则爆破也行,gg语法找后台也可以。

前台一处Insert盲注

这段代码位于: Include/web_email.php 文件中,可以看到变量 $msg_tel 未过滤,然后拼接进了 insert into 的sql语句中。

因为在 db_conn.php 中关闭了错误回显,那这里只能使用时间盲注的技巧来就进行注入了。

当配置文件中开启了邮件发送 $web_mailopen 的值就变成了1,这种场景下跑盲注动静有点大哦,需要控制好请求

验证延时注入是否生效:

管理员账号密码重置【复现】

2018.06.06 晚补充

今天在看 seebug 时发现审计过的这个 CMS 系统有爆 任意密码重置漏洞,在想当初审计时为什么没有注意到(果然还是太年轻呀…)。

首先通过关键词定位到人口文件:

$ ag -i '找回密码'
Include/web_email.php
20:// 找回密码
38:   $mailcontent="网站管理员你好:<br>你的邮箱是:".$postEmail."<br> 点击<a href='".$fhurl."?umail=".$postEmail."&type=ok' target='_blank'>找回密码</a>"

定位到找回密码的代码块在 Include/web_email.php 文件中

通过图中的代码可以看到,通过接收 $_GET["type"] 参数来控制进入的 if() 流程。

当然需要找回密码时,会随机生成一个 10~10000 之间的认证码交给 $fsjs,在通过 UPDATE 去更新 表 user_rzm 中的值。

这个时候想起该 CMS 在创建数据库的数据时,会默认写入一个管理员账号,文件在 install/semcms.sql 中有这样一段:

--
-- 转存表中的数据 `sc_user`
--

INSERT INTO `sc_user` (`ID`, `user_name`, `user_admin`, `user_ps`, `user_tel`, `user_qx`, `user_time`, `user_email`, `user_rzm`) VALUES
(3, '总账号', 'Admin', 'c4ca4238a0b923820dcc509a6f75849b', '333', '74,76,77,87,88,75,78,79,80,81,82,83,84,89,100,en', '2016-09-22 18:23:02', '41864438@qq.com', '2754');

这样我们就知道了后台管理员账号的邮箱,和初始验证码。

我们在看看重置密码的方法,在 $Type=="findok" 的时候:

当我们提交三个参数后,在 UPDATE 的 where 条件中只用到了两个,也没有做任何频率控制,通过邮箱加验证码就可以任意的重置密码了。

也没有做任何的频率控制,只要满足以下条件统统可以重置管理员密码:

  • 初始 总账号 邮箱和验证码未发生过改动
  • 初始验证码未发送过改动,可以通过 $Type=="fintpassword" 中的 sql 查询去爆破邮箱,然后再重置密码
  • 已知邮箱但验证码发生过更改,可以通过触发 $Type=="fintpassword" 中的代码去重置验证码,然后再爆破 $Type=="findok" 代码的验证实现重置密码

哈? user_name 被改了? 那就洗洗睡吧