Checkmk:通过链接多个错误进行远程代码执行

admin 2022年11月5日11:02:30评论62 views字数 8634阅读28分46秒阅读模式

Checkmk:通过链接多个错误进行远程代码执行

Checkmk 是使用 Python 和 C++ 开发的现代 IT 基础设施监控解决方案。根据供应商的网站,超过 2,000 名客户依赖 Checkmk。由于其目的,Checkmk 是一个中央组件,通常部署在公司网络的特权位置。这使其成为威胁参与者的高调目标。

为了帮助保护开源世界,我们决定查看 Checkmk 的开源版本,它基于 Nagios 监控核心并无缝集成 NagVis 以可视化地图和图表上的状态数据。在我们的研究中,我们发现了 Checkmk 及其 NagVis 集成中的多个漏洞,这些漏洞可以被未经身份验证的远程攻击者链接在一起,以完全接管运行 Checkmk 易受攻击版本的服务器。

在第一篇文章中,在三篇系列文章中,我们首先概述了所有已识别的漏洞以及对 Checkmk 架构的基本了解。此外,我们确定将已识别的漏洞链接在一起的灾难性影响。我们还深入研究了前两个漏洞的技术细节,这为未经身份验证的攻击者获得远程代码执行铺平了道路。

影响

我们在 Checkmk 及其 NagVis 与供应商分配的以下 CVSS 分数集成中发现了多个漏洞:

CVSS 9.1:watolib 的 auth.php 中的代码注入

CVSS 9.1:在 NagVis 中读取任意文件

CVSS 6.8:ajax_graph_images.py 中的换行注入

CVSS 5.0:代理接收器中的服务器端请求伪造

未经身份验证的远程攻击者可以将这些漏洞链接在一起,以在运行 Checkmk 2.1.0p10 及更低版本的服务器上执行代码:

我们通过利用其监控核心的特定功能验证了对开源原始版的利用。攻击者很可能可以使用类似的技术来利用运行企业版的服务器。

Checkmk 版本 2.1.0p12 修复了所有这些问题。我们强烈建议使用此版本之前的版本更新任何实例。

技术细节

在本节中,我们首先查看 Checkmk 的基本架构及其组件。基于此,我们概述了攻击者如何将已识别的漏洞链接在一起,并深入研究前两个漏洞的技术细节,这是获得未经身份验证的远程代码执行的完整链的开始。

背景

Checkmk 是一个类似于 Zabbix 或 Icinga 的 IT 基础设施监控解决方案。服务器、网络、应用程序等的配置和监控是通过 Web 界面完成的。这个面向用户的组件是用 Python 开发的,称为 Checkmk GUI。

为了从受监控的系统中检索附加信息,可以在这些系统上部署监控代理。负责注册代理并从这些代理接收数据的组件称为代理接收器。

下图概述了 Checkmk 的基本架构:

Checkmk:通过链接多个错误进行远程代码执行

Checkmk 默认在外部网络接口上公开两个端口:

TCP 端口 80:实际的 Web 界面

TCP 端口 8000:代理接收方

Web 界面的第一个组件是运行在 TCP 端口 80 上的 Apache Web 服务器,它用作反向代理。可以在单个主机上运行多个 Checkmk 实例。这些实例称为监控站点或简称站点。对于每个站点,都会生成一个专用的内部 Apache 服务器。外部反向代理的目的是将特定站点的请求映射到专用于所请求站点的相应内部 Apache 服务器。在上图中,站点监控映射到运行在 TCP 端口 5000 上的 Apache 服务器。从外部看,这个 Apache 服务器只能通过反向代理访问,因为它只监听 localhost。

站点专用的 Apache 服务器将请求转发到实际的 Checkmk GUI、Python WSGI 应用程序,或通过 FCGI 转发到 PHP 包装器,以便集成 NagVis PHP 组件。

Checkmk 的核心是监控核心,负责启动检查、收集数据、检测状态变化以及向 GUI 提供信息。Checkmk 企业版拥有自己的监控核心,而开源原始版使用 Nagios 监控核心。为了从中检索数据,核心提供了一个名为 Livestatus 的接口,该接口被实现为名为livestatus.o的 C++ Nagios 代理模块。该接口使用一种名为 Livestatus Query Language (LQL) 的专有协议,该协议类似于 HTTP 和 SQL。例如,查询所有处于DOWN ( 1 ) 或UNREACH ( 2 ) 状态的受监控主机的名称和 IP 地址,如下所示:

Checkmk:通过链接多个错误进行远程代码执行

响应可能如下所示:

Checkmk:通过链接多个错误进行远程代码执行

更高级的查询可以通过使用额外的标题来构建。每当 GUI 需要来自核心的一些数据时,它都会向它发送一个 LQL 查询,而核心则以请求的数据进行响应。

第二个可通过外部接口直接访问的组件是agent-receiver。agent-receiver 是一个 FastAPI Web 服务器,侦听 TCP 端口 8000,它为注册代理和从这些代理收集数据提供不同的路由。

通过对 Checkmk 组件的基本了解,让我们看看未经身份验证的攻击者如何能够将已识别的代码漏洞链接在一起以获得远程代码执行。

开发链

一些已识别的漏洞本身的实际影响有限。但是,恶意攻击者可以将它们链接在一起以实现远程代码执行。

下图总结了利用单个漏洞产生的能力,以及攻击者如何利用此能力利用以下漏洞进一步加强控制,最终导致未经身份验证的远程代码执行:

Checkmk:通过链接多个错误进行远程代码执行

攻击链从代理接收器 (1) 中的服务器端请求伪造开始,攻击者可以利用它来访问只能从 localhost 访问的端点。此端点容易受到换行注入 (2) 的攻击。这使攻击者能够伪造任意 LQL 查询,Checkmk GUI 使用这些查询从监控核心检索数据。攻击者可以利用此功能删除任意文件,进一步利用该功能绕过 NagVis 组件中的身份验证机制。

一旦攻击者获得了对 NagVis 组件的访问权限,就可以利用 NagVis 中经过身份验证的任意文件读取漏洞 (3) 来读取名为automation.secret的特殊 Checkmk 配置文件。通过访问该文件的内容,攻击者可以在自动化用户的上下文中访问 Checkmk GUI。通过利用名为watolib的 Checkmk GUI 子组件中的代码注入漏洞 (4),此访问可以进一步转变为远程代码执行,该漏洞会生成NagVis 集成所需的 名为auth.php的文件。

在对利用链进行了粗略的概述之后,让我们深入了解前两个代码漏洞的技术细节:

Checkmk:通过链接多个错误进行远程代码执行

代理接收器中的服务器端请求伪造

Checkmk 代理接收器是一个 FastAPI Web 服务器,默认情况下暴露在 TCP 端口 8000 上。大多数提供的端点将请求转发到 Checkmk REST API,它是暴露在 TCP 端口 80 上的 Checkmk GUI 的一部分。

名为/register_with_hostname的端点需要一个 POST 请求,其中包含通过 HTTP 基本身份验证提供的凭据以及正文中的两个 JSON 编码参数uuidhost_name。端点处理程序本身仅验证是否提供了任何凭据以及两个参数是否存在。

为了检索和验证由host_name参数标识的主机的主机配置,调用函数host_configuration

checkmk/agent-receiver/agent-receiver/endpoints.py

@agent_receiver_app.post(
   "/register_with_hostname",
   status_code=HTTP_204_NO_CONTENT,
)
async def register_with_hostname(
   *,
   credentials: HTTPBasicCredentials = Depends(security),
   registration_body: RegistrationWithHNBody,
) -> Response:
   _validate_registration_request(
       host_configuration(
           credentials,
           registration_body.host_name,
       )
   )

host_configuration函数通过调用函数 _forward_get将请求转发到 Checkmk REST API。用户提供的参数host_name被附加到目标 URL,没有任何清理或编码:

checkmk/agent-receiver/agent-receiver/checkmk_rest_api.py

def host_configuration(
   credentials: HTTPBasicCredentials,
   host_name: str,
) -> HostConfiguration:
   if (
       response := _forward_get(
           f"objects/host_config_internal/{host_name}",
           credentials,
       )
   ).status_code == HTTPStatus.OK:

缺乏清理和编码会导致有限的服务器端请求伪造 (SSRF) 漏洞。

起初,这个漏洞的影响似乎不是很大,因为 SSRF 仅限于对 Checkmk GUI 的主机名和端口的 GET 请求,攻击者甚至无法读取响应。但是,这为攻击者提供了利用第二个漏洞的基本能力。让我们来看看。

ajax_graph_images.py 中的换行注入

Checkmk GUI 仅提供最少数量的未经身份验证的端点。这大大减少了攻击面。未经身份验证的端点之一称为/ajax_graph_images.py,其端点处理程序在函数ajax_graph_images_for_notifications中实现。此端点的目的是为给定的主机或服务生成具有性能数据的图像。

尽管可以在未经身份验证的情况下访问此端点,但仅允许来自本地主机(127.0.0.1::1)的请求来限制访问:

checkmk/cmk/gui/plugins/metrics/graph_images.py

def ajax_graph_images_for_notifications(
   resolve_combined_single_metric_spec: Callable[
       [CombinedGraphSpec], Sequence[CombinedGraphMetricSpec]
   ],
) -> None:
   """Registered as `noauth:ajax_graph_images`."""
   if request.remote_ip not in ["127.0.0.1", "::1"]:
       raise MKUnauthenticatedException(
           _("You are not allowed to access this page (%s).") % request.remote_ip
       )

   with SuperUserContext():
       _answer_graph_image_request(resolve_combined_single_metric_spec)

在验证请求来自 localhost 后,调用函数_answer_graph_image_request。此函数验证是否提供了主机GET 参数,然后调用get_graph_data_from_livestatus

checkmk/cmk/gui/plugins/metrics/graph_images.py

def _answer_graph_image_request(
   resolve_combined_single_metric_spec: Callable[
       [CombinedGraphSpec], Sequence[CombinedGraphMetricSpec]
   ],
) -> None:
   try:
       host_name = request.get_ascii_input_mandatory("host")
       if not host_name:
           raise MKGeneralException(_('Missing mandatory "host" parameter'))
       ...
       try:
           row = get_graph_data_from_livestatus(site, host_name, service_description)

函数get_graph_data_from_livestatus通过 Livestatus 查询语言 (LQL) 接口检索给定主机的性能数据。在检查调用堆栈中的所有调用函数时,_ensure_connected函数引起了我们的注意:

checkmk/cmk/gui/sites.py

def _ensure_connected(user: Optional[LoggedInUser], force_authuser: Optional[UserId]) -> None:
   ...
   if force_authuser is None:
       request_force_authuser = request.get_str_input("force_authuser")
       force_authuser = UserId(request_force_authuser) if request_force_authuser else None
   ...
   _set_livestatus_auth(user, force_authuser)

虽然这是负责查询 LQL 接口的代码的内部函数部分,但访问了一个名为force_authuser的 GET 参数。进一步检查调用堆栈会发现这个 GET 参数被插入到 LQL 查询的AuthUser标头中,没有进行任何清理:

Checkmk:通过链接多个错误进行远程代码执行

AuthUser头用于限制对允许指定用户查看的数据的响应。然而,这对于我们的考虑并不是必不可少的。重要的方面是上面的AuthUser字符串包含 GET 参数force_authuser的值,并且该字符串被插入到发送到监控核心的最终 LQL 查询中。由于 GET 参数force_authuser未过滤,因此也可以在 LQL 查询中插入换行符 ( 0x0a )。

通常,外部攻击者无法访问易受攻击的端点/ajax_graph_images.py,因为它仅限于本地主机。当与代理接收器中的 SSRF 漏洞结合使用时,此假设不再有效。例如,SSRF 可用于触发具有以下 GET 参数的请求:

Checkmk:通过链接多个错误进行远程代码执行

此请求导致以下 LQL 查询发送到核心:

Checkmk:通过链接多个错误进行远程代码执行

通过在force_authuser参数中使用换行符,可以将其他标头注入到 LQL 查询中:

Checkmk:通过链接多个错误进行远程代码执行

生成的 LQL 查询包含附加标头:

Checkmk:通过链接多个错误进行远程代码执行

注入全新查询以使用其他表或命令的能力将进一步增加攻击面。攻击者可以尝试添加两个换行符并在这些字符之后插入一个新查询:

Checkmk:通过链接多个错误进行远程代码执行

但是,如果读取了两个后续的换行符,则 LQL 接口默认终止连接,这形成了单个查询的结尾。因此不评估第二个查询:

Checkmk:通过链接多个错误进行远程代码执行

可以通过利用KeepAlive标头来更改此行为。当此标头设置为on时,连接将保持活动状态。这样就可以注入全新的 LQL 查询:

Checkmk:通过链接多个错误进行远程代码执行

这会产生三个不同的 LQL 查询,它们分别进行处理。

查询一:

Checkmk:通过链接多个错误进行远程代码执行

查询 2:

Checkmk:通过链接多个错误进行远程代码执行

查询 3:

Checkmk:通过链接多个错误进行远程代码执行

第二个查询可以由攻击者完全控制。

凭借这种能力,攻击者实际上已经进入了 Checkmk 的核心。在本系列的下一篇文章中,我们将探索 LQL 接口作为新的攻击面,并了解开发人员实现中的一些细微差别如何防止或使攻击者绕过身份验证机制。

修补

Checkmk在版本 2.1.0p12 ( commit )中修补了代理接收器中受限的 SSRF 。根据我们的建议,/register_with_hostname的端点处理程序现在在将host_name参数插入 URL 之前对其进行URL 编码:

checkmk/agent-receiver/agent-receiver/checkmk_rest_api.py

from urllib.parse import quote
...

def _url_encode_hostname(host_name: str) -> str:
    ...
    return quote(host_name, safe="")  # '/' is not "safe" here
...

def host_configuration(...):
   ...
       response := _forward_get(
           f"objects/host_config_internal/{_url_encode_hostname(
host_name)}", ...)
   ...

当请求被转发到 Checkmk REST API 时,这可以防止攻击者访问其他端点而不是预期端点。

通过验证为AuthUser标头提供的值,还使用版本 2.1.0p12 ( commit ) 修补了换行注入漏洞

checkmk/livestatus/api/python/livestatus.py

# Pattern for allowed UserId values
validate_user_id_regex = re.compile(r"^[w_][-w.@_]*$")
...
   # Set user to be used in certain authorization domain
   def set_auth_user(self, domain: str, user: UserId) -> None:
       # Prevent setting AuthUser to values that would be rejected later. See Werk 14384.
       # Empty value is allowed and used to delete from auth_users dict.
       if user and validate_user_id_regex.match(user) is None:
           raise ValueError("Invalid user ID")

此外,还引入了对注入换行符的额外检查:

checkmk/livestatus/api/python/livestatus.py

   def build_query(self, query_obj: Query, add_headers: str) -> str:
       # Prevent injection of further livestatus commands inside AuthUser header.
       if "n" in self.auth_header[:-1]:
           raise MKLivestatusQueryError("Refusing to build query with invalid AuthUser header.")

这些补丁有效地防止攻击者在force_authuser参数中注入换行符。

时间线

日期

行动

2022-08-22

我们向 Checkmk 报告所有问题。

2022-08-23

供应商确认所有问题。

2022-09-15

供应商发布修补版本 2.1.0p12。

概括

在这三篇系列文章的第一篇中,我们简要介绍了 Checkmk 架构并概述了我们发现的漏洞,包括将它们链接在一起的严重影响。我们还对前两个漏洞进行了技术深入研究,这些漏洞使外部攻击者能够向监控核心发送任意 LQL 查询。

大多数漏洞的根本原因是缺乏对用户控制数据的清理。对于我们研究的两个漏洞也是如此。换行注入漏洞很难被发现,因为用户控制的数据是由调用堆栈深处的函数访问的,而不是直接在端点处理程序中。这通常是一种不好的模式,应该避免。

在本系列的下一篇文章中,我们将更详细地了解 LQL 接口,并得出攻击者伪造任意查询能力的影响。我们还将研究 Checkmk 的 NagVis 集成,以及由于一些特定的实现细节,如何利用上述能力绕过 NagVis 的身份验证。

最后,我们要非常感谢 Checkmk 团队快速响应我们的报告,以绝对透明的方式处理每个问题,并为所有报告的漏洞提供全面的补丁。

——来自sonar博客

网络安全等级保护安全物理环境测评培训PPT

网络安全等级保护实施指南培训PPT

数据安全风险评估清单

美国关键信息基础设施数据泄露的成本

  1. 开启等级保护之路:GB 17859网络安全等级保护上位标准
  2. 网络安全等级保护:等级保护测评过程及各方责任
  3. 网络安全等级保护:政务计算机终端核心配置规范思维导图
  4. 网络安全等级保护:什么是等级保护?
  5. 网络安全等级保护:信息技术服务过程一般要求
  6. 闲话等级保护:网络安全等级保护基础标准(等保十大标准)下载
  7. 闲话等级保护:什么是网络安全等级保护工作的内涵?
  8. 闲话等级保护:网络产品和服务安全通用要求之基本级安全通用要求
  9. 闲话等级保护:测评师能力要求思维导图
  10. 闲话等级保护:应急响应计划规范思维导图
  11. 闲话等级保护:浅谈应急响应与保障
  12. 闲话等级保护:如何做好网络总体安全规划
  13. 闲话等级保护:如何做好网络安全设计与实施
  14. 闲话等级保护:要做好网络安全运行与维护
  15. 闲话等级保护:人员离岗管理的参考实践
  16. 信息安全服务与信息系统生命周期的对应关系
  17. 工业控制系统安全:信息安全防护指南
  18. 工业控制系统安全:工控系统信息安全分级规范思维导图
  19. 工业控制系统安全:DCS防护要求思维导图
  20. 工业控制系统安全:DCS管理要求思维导图
  21. 工业控制系统安全:DCS评估指南思维导图
  22. 工业控制安全:工业控制系统风险评估实施指南思维导图
  23. 工业控制系统安全:安全检查指南思维导图(内附下载链接)
  24. 工业控制系统安全:DCS风险与脆弱性检测要求思维导图

原文始发于微信公众号(祺印说信安):Checkmk:通过链接多个错误进行远程代码执行

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年11月5日11:02:30
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Checkmk:通过链接多个错误进行远程代码执行http://cn-sec.com/archives/1392504.html

发表评论

匿名网友 填写信息