最近在审计yunucms,审计了几个后台漏洞,有XSS和文件泄露几个。

yunucms介绍

云优CMS是一款基于TP5.0框架为核心开发的一套免费+开源的城市分站内容管理系统。云优CMS前身为远航CMS。云优CMS于2017年9月上线全新版本,二级域名分站,内容分站独立,七牛云存储,自定义字段,自定义表单,自定义栏目权限,自定义管理权限等众多功能深受用户青睐。

建站

官网下载源码并进行过安装

需要注意的是需要填云账号,我去官网注册了一个随便填上了,账号testqwe,密码123456,手机号利用的在线短信注册的

填上MySQL密码即可

前台界面

漏洞复现

XSS

漏洞测试

后台TAG管理模块

进行添加TAG

在名称处填入XSS代码并提交

返回模块即可看到效果

查看源码,发现已经插入

查看数据库

代码审计

http://127.0.0.1/index.php?s=/admin/tagurl/addtagurl

该cms路由为目录/文件/方法,直接查看方法

public function addTagurl()
    {
        if(request()->isAjax()){ # 判断是否是ajax请求
            $param = input('post.'); # 获取参数
            $tagurl = new TagurlModel();
            $flag = $tagurl->insertTagurl($param); # 将结果进行保存并返回响应
            return json(['code' => $flag['code'], 'data' => $flag['data'], 'msg' => $flag['msg']]);
        }
        return $this->fetch();
    }

跟进insertTagurl方法

public function insertTagurl($param)
    {
        try{
            $result = $this->allowField(true)->save($param); # 保存当前数据对象
            if(false === $result){            
                return ['code' => -1, 'data' => '', 'msg' => $this->getError()];
            }else{
                return ['code' => 1, 'data' => '', 'msg' => '添加TAG成功'];
            }
        }catch( PDOException $e){
            return ['code' => -2, 'data' => '', 'msg' => $e->getMessage()];
        }
    }

继续跟进save方法

if (!empty($data)) {
            // 数据自动验证
            if (!$this->validateData($data)) { # 验证集为空,直接返回true
                return false;
            }
            // 数据对象赋值
            foreach ($data as $key => $value) {
                $this->setAttr($key, $value, $data); # 将参数赋值给$this->data数组
            }
            if (!empty($where)) {
                $this->isUpdate = true;
            }
        }

......        

$result = $this->getQuery()->insert($this->data);

......
``

validateData方法需要验证集,而本身没有传入

protected function validateData($data, $rule = null, $batch = null)
    {
        $info = is_null($rule) ? $this->validate : $rule;

        if (!empty($info)) {
            ......
        }
        return true;
    }

$this->validate参数为空,因此直接返回true

跟进insert方法

.....
        // 生成SQL语句
        $sql = $this->builder->insert($data, $options, $replace);
        $bind = $this->getBind();
        if ($options['fetch_sql']) {
            // 获取实际执行的SQL语句
            return $this->connection->getRealSql($sql, $bind);
        }

        // 执行操作
        $result = $this->execute($sql, $bind);

fetch_sql变量为false,跟进execute方法

......
    if ($procedure) { # false
                $this->bindParam($bind);
            } else {
                $this->bindValue($bind);
            }
......

最后跟进参数绑定方法

protected function bindValue(array $bind = [])
    {
        foreach ($bind as $key => $val) {
            // 占位符
            $param = is_numeric($key) ? $key + 1 : ':' . $key;
            if (is_array($val)) {
                if (PDO::PARAM_INT == $val[1] && '' === $val[0]) {
                    $val[0] = 0;
                }
                $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]);
            } else {
                $result = $this->PDOStatement->bindValue($param, $val);
            }
            if (!$result) {
                throw new BindParamException(
                    "Error occurred  when binding parameters '{$param}'",
                    $this->config,
                    $this->getLastsql(),
                    $bind
                );
            }
        }
    }

可以看到最后是调用PDO对象对参数进行的绑定,除此之外并没有任何过滤,因此XSS代码可插入并执行

数据库泄露

漏洞测试

在后台系统管理->数据库管理模块将所有数据库备份

查看本地文件,所有备份保存在data目录下,发现名命是以时间命名,可以直接爆破得到

从前台访问并下载

下载完成后打开,泄露所有数据库信息

代码审计

POST /index.php?s=/admin/data/export HTTP/1.1

public function export($ids = null, $id = null, $start = null) {
        $Request = Request::instance();
        if ($Request->isPost() && !empty($ids) && is_array($ids)) { //初始化
            $path = config('data_backup_path');
            is_dir($path) || mkdir($path, 755, true);
            //读取备份配置
            $config = [
                'path' => realpath($path) . DIRECTORY_SEPARATOR,
                'part' => config('data_backup_part_size'),
                'compress' => config('data_backup_compress'),
                'level' => config('data_backup_compress_level'),
            ];

            //检查是否有正在执行的任务
            $lock = "{$config['path']}backup.lock";
            if (is_file($lock)) {
                return $this->error('检测到有一个备份任务正在执行,请稍后再试,或手动删除"'.$lock.'"后重试!');
            }
            file_put_contents($lock, $Request->time()); //创建锁文件
            //检查备份目录是否可写
            is_writeable($config['path']) || $this->error('备份目录不存在或不可写,请检查后重试!');
            session('backup_config', $config);

            //生成备份文件信息
            $file = [
                'name' => date('Ymd-His', $Request->time()),
                'part' => 1,
            ];
            session('backup_file', $file);
            //缓存要备份的表
            session('backup_tables', $ids);

            //创建备份文件
            $Database = new \com\Database($file, $config);
            if (false !== $Database->create()) {
                $tab = ['id' => 0, 'start' => 0];
                return $this->success('初始化成功!', '', ['tables' => $ids, 'tab' => $tab]);
            } else {
                return $this->error('初始化失败,备份文件创建失败!');
            }
        }
......

可以看到,备份文件的命名用的time方法,跟进

public function time($float = false)
    {
        return $float ? $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME'];
    }

可以看到利用REQUEST_TIME进行构造文件名,因此可以直接爆破得到并下载。

结束语

后台漏洞现在不太受待见,总觉得后台都进不去所以后台漏洞用处不大,不过后台漏洞更多的是利用组合拳。先拿到后台权限或者直接越权,拿下主机也是迟早的事。后台漏洞同样不可小觑。

文中如果有什么错误的地方还请各位师傅指教。

点击收藏 | 2 关注 | 2
  • 动动手指,沙发就是你的了!
登录 后跟帖