这个漏洞目前影响至禅道最新版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_**group、zt_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
④ 结果成功利用高层管理用户组的账号添加了一个管理员组的账号