我的编程空间,编程开发者的网络收藏夹
学习永远不晚

开源项目CRMEB 任意文件下载漏洞分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

开源项目CRMEB 任意文件下载漏洞分析

项目地址:https://github.com/crmeb/CRMEB

在这里插入图片描述
下载源码后来到app/adminapi/controller/v1/marketing/live/LiveGoods.php文件add函数。

源码如下:

public function add()    {        [$goods_info] = $this->request->postMore([            ['goods_info', []]        ], true);        if (!$goods_info) return app('json')->fail('请选择商品');        foreach ($goods_info as $goods) {            if (!$goods['id']) return app('json')->fail('请选择商品');            if (!$goods['store_name']) return app('json')->fail('请输入名称');            if (!$goods['image']) return app('json')->fail('请选择背景图');            if (!$goods['price']) return app('json')->fail('请输入直播价格');            if ($goods['price'] <= 0) return app('json')->fail('直播价格必须大于0');        }        $this->services->add($goods_info);        return app('json')->success('添加成功');    }

函数从前端接受一个goods_info参数赋值于变量$goods_info

[$goods_info] = $this->request->postMore([            ['goods_info', []]        ], true);

通过跟踪$goods_info参数进入$services对象的add函数

$this->services->add($goods_info);

在本类中可以看到services声明为类LiveGoodsServices

public function __construct(App $app, LiveGoodsServices $services)    {        parent::__construct($app);        $this->services = $services;    }

来到文件app/services/activity/live/LiveGoodsServices.php,函数add源码如下:

public function add(array $goods_info)    {        $product_ids = array_column($goods_info, 'id');        $this->create($product_ids);        $miniUpload = MiniProgramService::materialTemporaryService();                $download = app()->make(DownloadImageService::class);        $dataAll = $data = [];        $time = time();        foreach ($goods_info as $product) {            $data = [                'product_id' => $product['id'],                'name' => Str::substrUTf8($product['store_name'], 12, 'UTF-8', ''),                'cover_img' => $product['image'] ?? '',                'price_type' => 1,                'cost_price' => $product['cost_price'] ?? 0.00,                'price' => $product['price'] ?? 0.00,                'url' => 'pages/goods_details/index?id=' . $product['id'],                'sort' => $product['sort'] ?? 0,                'add_time' => $time            ];            try {                $path = root_path() . 'public' . $download->thumb(true)->downloadImage($data['cover_img'])['path'];                $coverImgUrl = $miniUpload->uploadImage($path)->media_id;                @unlink($path);            } catch (\Throwable $e) {                Log::error('添加直播商品图片错误,原因:' . $e->getMessage());                $coverImgUrl = $data['cover_img'];            }            $res = MiniProgramService::addGoods($coverImgUrl, $data['name'], $data['price_type'], $data['url'], floatval($data['price']));            $data['goods_id'] = $res['goodsId'];            $data['audit_id'] = $res['auditId'];            $data['audit_status'] = 1;            $dataAll[] = $data;        }        if (!$goods = $this->dao->saveAll($dataAll)) {            throw new AdminException('添加商品失败');        }        return true;    }

继续跟踪$goods_info变量,函数将$goods_info数组中的信息赋值给$data

foreach ($goods_info as $product) {            $data = [                'product_id' => $product['id'],                'name' => Str::substrUTf8($product['store_name'], 12, 'UTF-8', ''),                'cover_img' => $product['image'] ?? '',                'price_type' => 1,                'cost_price' => $product['cost_price'] ?? 0.00,                'price' => $product['price'] ?? 0.00,                'url' => 'pages/goods_details/index?id=' . $product['id'],                'sort' => $product['sort'] ?? 0,                'add_time' => $time            ];

继续往下看,将$data数组的cover_img值传递给downloadImage函数,继续跟进

$path = root_path() . 'public' . $download->thumb(true)->downloadImage($data['cover_img'])['path'];

来到文件crmeb/services/DownloadImageService.php,函数downloadImage源码如下:

public function downloadImage(string $url, $name = '')    {        if (!$name) {            //TODO 获取要下载的文件名称            $downloadImageInfo = $this->getImageExtname($url);            $name = $downloadImageInfo['file_name'];            if (!$name) throw new ValidateException('上传图片不存在');        }        if (strstr($url, 'http://') === false && strstr($url, 'https://') === false) {            $url = 'http:' . $url;        }        $url = str_replace('https://', 'http://', $url);        if ($this->path == 'attach') {            $date_dir = date('Y') . DIRECTORY_SEPARATOR . date('m') . DIRECTORY_SEPARATOR . date('d');            $to_path = $this->path . '/' . $date_dir;        } else {            $to_path = $this->path;        }        $upload = UploadService::init(1);        if (!file_exists($upload->uploadDir($to_path) . '/' . $name)) {            ob_start();            readfile($url);            $content = ob_get_contents();            ob_end_clean();            $size = strlen(trim($content));            if (!$content || $size <= 2) throw new ValidateException('图片流获取失败');            if ($upload->to($to_path)->down($content, $name) === false) {                throw new ValidateException('图片下载失败');            }            $imageInfo = $upload->getDownloadInfo();            $path = $imageInfo['dir'];            if ($this->thumb) {                Image::open(root_path() . 'public' . $path)->thumb($this->thumbWidth, $this->thumHeight)->save(root_path() . 'public' . $path);                $this->thumb = false;            }        } else {            $path = '/uploads/' . $to_path . '/' . $name;            $imageInfo['name'] = $name;        }        $date['path'] = $path;        $date['name'] = $imageInfo['name'];        $date['size'] = $imageInfo['size'] ?? '';        $date['mime'] = $imageInfo['type'] ?? '';        $date['image_type'] = 1;        $date['is_exists'] = false;        return $date;    }

可控变量$data['cover_img']作为参数传递给$url,继续跟踪$url,函数从$rul指定的地址获取文件内容,并保存在变量$content

ob_start();readfile($url);$content = ob_get_contents();ob_end_clean();

跟踪$content,将$content传入了函数$down

$upload->to($to_path)->down($content, $name)

来到文件crmeb/services/upload/storage/Local.php,函数down源码如下:

public function down(string $fileContent, string $key = null)    {        if (!$key) {            $key = $this->saveFileName();        }        $dir = $this->uploadDir($this->path);        if (!$this->validDir($dir)) {            return $this->setError('Failed to generate upload directory, please check the permission!');        }        $fileName = $dir . '/' . $key;        file_put_contents($fileName, $fileContent);        $this->downFileInfo->downloadInfo = new File($fileName);        $this->downFileInfo->downloadRealName = $key;        $this->downFileInfo->downloadFileName = $key;        $this->downFileInfo->downloadFilePath = $this->defaultPath . '/' . $this->path . '/' . $key;        return $this->downFileInfo;    }

继续跟踪$fileContent,函数将$fileContent的内容写入到文件

$fileName

file_put_contents($fileName, $fileContent);

现在再来看看$fileNmae的值,回到文件crmeb/services/DownloadImageService.php的函数downloadImage,$url是我们可以控制的值,传递给了本类的getImageExtname函数

$downloadImageInfo = $this->getImageExtname($url);

getImageExtname源码如下,大概意思就是将$url链接进行md5加密后作为文件的新名字复制给file_name然后返回给downloadImage函数:

public function getImageExtname($url = '', $ex = 'jpg')    {        $_empty = ['file_name' => '', 'ext_name' => $ex];        if (!$url) return $_empty;        if (strpos($url, '?')) {            $_tarr = explode('?', $url);            $url = trim($_tarr[0]);        }        $arr = explode('.', $url);        if (!is_array($arr) || count($arr) <= 1) return $_empty;        $ext_name = trim($arr[count($arr) - 1]);        $ext_name = !$ext_name ? $ex : $ext_name;        return ['file_name' => md5($url) . '.' . $ext_name, 'ext_name' => $ext_name];    }

downloadImage函数将返回的file_name值赋值给变量$name

$name = $downloadImageInfo['file_name'];

然后将$name作为第二个参数传入down

$upload->to($to_path)->down($content, $name)

回到down函数,将传入的$name作为参数$key值拼接到变量$dir作为文件的位置,这样一来我们就可以控制函数file_put_contents的内容并且知道文件的位置

$fileName = $dir . '/' . $key;

但是有个问题,回到文件app/services/activity/live/LiveGoodsServices.php的函数add中发现,我们最后存储的文件会被使用@unlink($path)删除,这里可以通过不配置微信的appid在执行$miniUpload->uploadImage($path)->media_id;时抛出异常来跳过@unlink($path)的执行,执行catch里的代码

try {                $path = root_path() . 'public' . $download->thumb(true)->downloadImage($data['cover_img'])['path'];                $coverImgUrl = $miniUpload->uploadImage($path)->media_id;                @unlink($path);            } catch (\Throwable $e) {                Log::error('添加直播商品图片错误,原因:' . $e->getMessage());                $coverImgUrl = $data['cover_img'];            }

本地搭建环境后登陆后台
在服务器上放上恶意代码并开启文件下载服务
在这里插入图片描述
进入后台,如果下面页面有设置appid则将其设置为空
在这里插入图片描述
进入到后台的直播商品管理界面
在这里插入图片描述
点击添加商品,选择商品后提交抓包,更改image参数为我们服务器上恶意文件地址
在这里插入图片描述

然后将服务器文件地址进行md5
在这里插入图片描述

访问路径如下:

http://domain.com/uploads/attach/{year}/{month} /day}/{远程文件url的md5编码}.php

成功执行代码
在这里插入图片描述

来源地址:https://blog.csdn.net/heartself/article/details/127522470

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

开源项目CRMEB 任意文件下载漏洞分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录