代码审计之逃不过的命运
2022-8-3 15:10:18 Author: www.secpulse.com(查看原文) 阅读量:40 收藏

噩耗传来


就这样被你征服,喝下你藏好的毒。。。。真的是爆头【抱头】唱征服。

亡羊补牢

漏洞复现

  • • 后台增加可执行的语句。

  • • 创建非管理组的用户

  • • 换浏览器登录

选择小明账户登录,此时注意,添加的路由规则,在这个角色下,要有权限。

代码分析

入口文件中构造方法。里面存在父级的方法,跟进去。

public function _initialize(){    parent::_initialize();    //移除HTML标签    $this->request->filter('trim,strip_tags,htmlspecialchars');}进入到父级的方法体。public function _initialize(){        $modulename = $this->request->module();        $controllername = Loader::parseName($this->request->controller());        $actionname = strtolower($this->request->action());
       $path = str_replace('.', '/', $controllername) . '/' . $actionname;
       // 定义是否Addtabs请求        !defined('IS_ADDTABS') && define('IS_ADDTABS', input("addtabs") ? true : false);
       // 定义是否Dialog请求        !defined('IS_DIALOG') && define('IS_DIALOG', input("dialog") ? true : false);
       // 定义是否AJAX请求        !defined('IS_AJAX') && define('IS_AJAX', $this->request->isAjax());
       $this->auth = Auth::instance();
       // 设置当前请求的URI        $this->auth->setRequestUri($path);        // 检测是否需要验证登录        if (!$this->auth->match($this->noNeedLogin)) {            //检测是否登录            if (!$this->auth->isLogin()) {                Hook::listen('admin_nologin', $this);                $url = Session::get('referer');                $url = $url ? $url : $this->request->url();                if ($url == '/') {                    $this->redirect('index/login', [], 302, ['referer' => $url]);                    exit;                }                $this->error(__('Please login first'), url('index/login', ['url' => $url]));            }            // 判断是否需要验证权限            if (!$this->auth->match($this->noNeedRight)) {                // 判断控制器和方法判断是否有对应权限                if (!$this->auth->check($path)) {                    Hook::listen('admin_nopermission', $this);                    $this->error(__('You have no permission'), '');                }            }        }
       // 非选项卡时重定向        if (!$this->request->isPost() && !IS_AJAX && !IS_ADDTABS && !IS_DIALOG && input("ref") == 'addtabs') {            $url = preg_replace_callback("/([\?|&]+)ref=addtabs(&?)/i", function ($matches) {                return $matches[2] == '&' ? $matches[1] : '';            }, $this->request->url());            if (Config::get('url_domain_deploy')) {                if (stripos($url, $this->request->server('SCRIPT_NAME')) === 0) {                    $url = substr($url, strlen($this->request->server('SCRIPT_NAME')));                }                $url = url($url, '', false);            }            $this->redirect('index/index', [], 302, ['referer' => $url]);            exit;        }
       // 设置面包屑导航数据        $breadcrumb = $this->auth->getBreadCrumb($path);

在父级方法体里有获取导航的数据方法,getBreadCrumb。接着进去

public function getBreadCrumb($path = '')
{
        if ($this->breadcrumb || !$path) {         
           return $this->breadcrumb;        
        }        
        $titleArr = [];        
        $menuArr = [];        
        $urlArr = explode('/', $path);        
        foreach ($urlArr as $index => $item) {        
            $pathArr[implode('/', array_slice($urlArr, 0, $index + 1))] = $index;        
        }        
        if (!$this->rules && $this->id) {          
            $this->getRuleList();        
        }

此方法中的getRuleList里,存在获得规则具体逻辑。接着进来

public function getRuleList($uid = null){    $uid = is_null($uid) ? $this->id : $uid;    return parent::getRuleList($uid);}这里又跳到了父链,在父级中的getRuleList方法体如下。 public function getRuleList($uid){        static $_rulelist = []; //保存用户验证通过的权限列表        if (isset($_rulelist[$uid])) {            return $_rulelist[$uid];        }        if (2 == $this->config['auth_type'] && Session::has('_rule_list_' . $uid)) {            return Session::get('_rule_list_' . $uid);        }
       // 读取用户规则节点        $ids = $this->getRuleIds($uid);        if (empty($ids)) {            $_rulelist[$uid] = [];            return [];        }
       // 筛选条件        $where = [            'status' => 'normal'        ];        if (!in_array('*', $ids)) {            $where['id'] = ['in', $ids];        }        //读取用户组所有权限规则        $this->rules = Db::name($this->config['auth_rule'])->where($where)->field('id,pid,condition,icon,name,title,ismenu')->select();
       //循环规则,判断结果。        $rulelist = []; //        if (in_array('*', $ids)) {            $rulelist[] = "*";        }        foreach ($this->rules as $rule) {            //超级管理员无需验证condition            if (!empty($rule['condition']) && !in_array('*', $ids)) {                //根据condition进行验证                $user = $this->getUserInfo($uid); //获取用户信息,一维数组                $command = preg_replace('/\{(\w*?)\}/', '$user[\'\\1\']', $rule['condition']);                @(eval('$condition=(' . $command . ');'));                if ($condition) {                    $rulelist[$rule['id']] = strtolower($rule['name']);                }

在这段截取的代码中,尾部的位置有个明晃晃的eval几个大字啊。这是要干嘛,这是要留后门啊。寻求开发的说辞,截图如下:


不明所以然的观众不少哇。说明文档中没有提及怎么用,回归源码找注释。


注释说,如果分数大于,或者小于什么,怎么怎么样。在修复的代码里有这么一段正则匹配。"/^(w+)s?([><=]+)s?(.*)$/"。里面包含大小于号,确实是一个条件的判断。正常的逻辑权限控制,都是某个角色有哪个权限,这里使用的场景,可能角色里面还能再判断细分吧。不过,一直没用过。未解其中味啊。

二:为什么非要小权限,看下图:

//超级管理员无需验证condition
if (!empty($rule['condition']) && !in_array('*', $ids)) {

这里判断了是否是*号,是*号就是超级管理员。求证数据库。截图下:

其他的都是数字,只有admin是*。

修复

将以下代码

$command = preg_replace('/{(w*?)}/', '$user['\1']', $rule['condition']);
@(eval('$condition=(' . $command . ');'));
if ($condition) {
    $rulelist[$rule['id']] = strtolower($rule['name']);
}

替换成

$nums = 0;
$condition = str_replace(['&&', '||'], "rn", $rule['condition']);
$condition = preg_replace('/{(w*?)}/', '\1', $condition);
$conditionArr = explode("rn", $condition);
foreach ($conditionArr as $index => $item) {
    preg_match("/^(w+)s?([><=]+)s?(.*)$/", trim($item), $matches);    
    if ($matches && isset($user[$matches[1]]) && version_compare($user[$matches[1]], $matches[3], $matches[2])) {      
        $nums++;    
    }
}
if ($conditionArr && ((stripos($rule['condition'], "||") !== false && $nums > 0) || count($conditionArr) == $nums)) { 
    $rulelist[$rule['id']] = strtolower($rule['name']);
}

总结

如果你担心某种情况发生,那么它就更有可能发生----墨菲定律第四条。感觉RCE的东西还是在危险函数处多用心,攻防都在此。

E

N

D

本文作者:TideSec

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/184672.html


文章来源: https://www.secpulse.com/archives/184672.html
如有侵权请联系:admin#unsafe.sh