这个漏洞目前影响至禅道最新版9.1.2(2017-04-19)

一、权限控制

①禅道的权限控制在module\common\model.php中的checkPriv()函数

// 1106行
public function checkPriv()
{
    $module = $this->app->getModuleName();
    $method = $this->app->getMethodName();
    if(isset($this->app->user->modifyPassword) and $this->app->user->modifyPassword and $module != 'my' and $method != 'changepassword') die(js::locate(helper::createLink('my', 'changepassword')));
    if($this->isOpenMethod($module, $method)) return true;
    if(!$this->loadModel('user')->isLogon() and $this->server->php_auth_user) $this->user->identifyByPhpAuth();
    if(!$this->loadModel('user')->isLogon() and $this->cookie->za) $this->user->identifyByCookie();

    if(isset($this->app->user))
    {
        if(!commonModel::hasPriv($module, $method)) $this->deny($module, $method);
    }
    else
    {
        $referer  = helper::safe64Encode($this->app->getURI(true));
        die(js::locate(helper::createLink('user', 'login', "referer=$referer")));
    }
}

②它调用了hasPirv()函数判断是否有权限

/ 1135行
public static function hasPriv($module, $method)
{
    global $app, $lang;

    /* Check is the super admin or not. */
    if($app->user->admin) return true; 

    /* If not super admin, check the rights. */
    $rights  = $app->user->rights['rights'];
    $acls    = $app->user->rights['acls'];
    $module  = strtolower($module);
    $method  = strtolower($method);
    if(isset($rights[$module][$method]))
    {
        if(empty($acls['views'])) return true;
        $menu = isset($lang->menugroup->$module) ? $lang->menugroup->$module : $module;
        $menu = strtolower($menu);
        if($menu != 'qa' and !isset($lang->$menu->menu)) return true;
        if($menu == 'my' or $menu == 'index' or $module == 'tree') return true;
        if($module == 'company' and $method == 'dynamic') return true;
        if($module == 'action' and $method == 'editcomment') return true;
        if(!isset($acls['views'][$menu])) return false;
        return true;
    }
    return false;
}

③函数判断了$rights[$module][$method]是否存在,这个的定义在module\user\model.php中的authorize()函数。

// 675行
public function authorize($account)
{
    $account = filter_var($account, FILTER_SANITIZE_STRING);
    if(!$account) return false;

    $rights = array();
    if($account == 'guest')
    {
        $acl  = $this->dao->select('acl')->from(TABLE_GROUP)->where('name')->eq('guest')->fetch('acl');
        $acls = empty($acl) ? array() : json_decode($acl, true);

        $sql = $this->dao->select('module, method')->from(TABLE_GROUP)->alias('t1')->leftJoin(TABLE_GROUPPRIV)->alias('t2')
            ->on('t1.id = t2.group')->where('t1.name')->eq('guest');
    }
    else
    {
        $groups = $this->dao->select('t1.acl')->from(TABLE_GROUP)->alias('t1')
            ->leftJoin(TABLE_USERGROUP)->alias('t2')->on('t1.id=t2.group')
            ->where('t2.account')->eq($account)
            ->fetchAll();
        $acls = array();
        $viewAllow    = false;
        $productAllow = false;
        $projectAllow = false;
        foreach($groups as $group)
        {
            if(empty($group->acl))
            {
                $productAllow = true;
                $projectAllow = true;
                $viewAllow    = true;
                break;
            }
            $acl = json_decode($group->acl, true);
            if(empty($acl['products'])) $productAllow = true;
            if(empty($acl['projects'])) $projectAllow = true;
            if(empty($acls) and !empty($acl))
            {
                $acls = $acl;
                continue;
            }

            if(!empty($acl['views'])) $acls['views'] = array_merge($acls['views'], $acl['views']);
            if(!empty($acl['products'])) $acls['products'] = !empty($acls['products']) ? array_merge($acls['products'], $acl['products']) : $acl['products'];
            if(!empty($acl['projects'])) $acls['projects'] = !empty($acls['projects']) ? array_merge($acls['projects'], $acl['projects']) : $acl['projects'];
        }

        if($productAllow) $acls['products'] = array();
        if($projectAllow) $acls['projects'] = array();
        if($viewAllow)    $acls = array();

        $sql = $this->dao->select('module, method')->from(TABLE_USERGROUP)->alias('t1')->leftJoin(TABLE_GROUPPRIV)->alias('t2')
            ->on('t1.group = t2.group')
            ->where('t1.account')->eq($account);
    }

    $stmt = $sql->query();
    if(!$stmt) return array('rights' => $rights, 'acls' => $acls);
    while($row = $stmt->fetch(PDO::FETCH_ASSOC))
    {
        $rights[strtolower($row['module'])][strtolower($row['method'])] = true;
    }
    return array('rights' => $rights, 'acls' => $acls);
}

④总的来说,就是数据库中中三张表:zt_**groupzt_grouppriv以及ztusergroup。第一张表zt**group是总的控制权限,可以说是acl,默认情况下,zt_group表中的acl字段为空,也就是这张表暂时没有起作用。第三张表存放的是zt_usergroup,它存放的是用户所对应的管理组ID;第二张表zt_grouppriv存放的是管理组所对应的module以及method权限。也就是说,只有zt_grouppriv中用户组对应了相应module以及method,当前用户才有权限进行访问。

二、**修改权限**

禅道有两个地方可以控制权限:一个是添加账号的时候create()函数,另外一个是修改账号资料的时候edit()函数,它们都位于module\user\control.php,最后数据库操作都是对应的module\user\model.php中的create()以及update()函数,它们对于权限修改的操作分别如下,其实就是in**sert以及update**操作而已。

// 236行,create()函数部分内容
if($this->post->group)
{
    $data = new stdClass();
    $data->account = $this->post->account;
    $data->group   = $this->post->group;
    $this->dao->insert(TABLE_USERGROUP)->data($data)->exec();
}
// 398行,update函数部分内容
if(isset($_POST['groups']))
{
    $this->dao->delete()->from(TABLE_USERGROUP)->where('account')->eq($this->post->account)->exec();
    foreach($this->post->groups as $groupID)
    {
        $data          = new stdclass();
        $data->account = $this->post->account;
        $data->group   = $groupID;
        $this->dao->replace(TABLE_USERGROUP)->data($data)->exec();
    }
}

三、逻辑冲突

系统权限最高的就是系统管理员,对应的group_id就是1,它拥有所有所有模块的权限。

① 哪些用户组拥有修改用户组的权限

也就是时候用户组1和9拥有修改管理组的权限,问题就出现在这里:用户组9是高层管理组,也就是说他的权限是低于管理员组的,而他却可以修改别人的用户组为管理员组

四**漏洞复现**

①添加一个group_id=9的账户test

② 登录test,添加账户ceshi1234

③ 抓包,将用户组修改为1

④ 结果成功利用高层管理用户组的账号添加了一个管理员组的账号

源链接

Hacking more

...