Zabbix是一个非常流行的开源监控平台,用于收集、集中和跟踪整个基础设施的CPU负载和网络流量等指标。它与Pandora FMS和Nagios等解决方案非常相似。由于Zabbix的受欢迎程度、功能以及在大多数公司网络中的特权地位,它是威胁参与者的一个引人注目的目标。一家专门收购安全漏洞的公共漏洞代理公司也公开宣布对该软件感兴趣。
我们在Zabbix的客户端会话实现中发现了一个高度严重的漏洞,该漏洞可能会导致整个网络受损。在本文中,我们将介绍不同类型的会话存储,并讨论如何确保实现的安全性。然后,我们描述了我们在Zabbix中发现的漏洞的技术细节、其影响以及如何预防。让我们投入其中吧!
会话都是关于跨多个HTTP请求存储状态,设计上是无状态的。为此,应用程序通常会向每个客户机传递一个唯一的标识符;他们必须将其与未来的请求一起传输。然后,服务器可以加载相关信息,无论它是否存储在内存、数据库、本地文件系统等中。这就是我们通常所说的服务器端会话存储。
这种历史性的方法工作得很好,但与现代web应用程序的开发和部署方式相比存在缺点。例如,它不能很好地扩展:如果后端服务被拆分到多个服务器上,如何确保一个会话在多个服务甚至整个服务器组中可用?
因此,开发人员在客户端引入了会话存储。他们现在需要在每个请求中发送一份状态副本,而不是将会话标识符分配给客户端。像ASP和Java这样的技术栈将这个概念包装在一种叫做视图状态的东西中,但是现在非常常见的是依赖JSON Web令牌(JWT)标准。
这两种方法的目标都是在客户端安全地存储数据,但后端服务仍然可以确保其真实性和完整性:它需要使用加密技术来提供这些保证。尽管存在错误配置的风险(弱机密、对破坏的加密算法的支持)以及撤销JWT的固有困难,但这基本上是一种安全的方式。
在这种情况下,不能混淆加密和身份验证提供的安全保证。虽然加密数据在未受过教育的人看来可能“安全”,但后端服务无法检测会话数据是否被客户端更改。使用ECB之类的加密模式,攻击者甚至可以在不知道密钥的情况下伪造有效的任意密文!
为了演示由于客户端会话代码的不安全设计和实现而可能产生的风险,让我们看看我们在Zabbix中发现的两个漏洞的技术细节。
Zabbix代理:在所有受监控节点上运行的服务,在Zabbix服务器请求时收集信息;
Zabbix服务器:它连接到Zabbix代理以收集监控数据,并在达到配置的阈值时发出警报;
Zabbix代理:将单个Zabbix服务器与数百个Zabbix代理相关联可能非常昂贵,在某些网络拓扑中很难部署。Zabbix代理实例旨在集中整个区域的数据,并将收集到的数据报告给Zabbix主服务器;
Zabbix Web前端:与Zabbix服务器的接口,通过TCP和共享数据库进行通信。系统管理员使用此仪表板访问收集的监控数据并配置Zabbix服务器(例如,列出主机、在Zabbix代理上运行脚本)。
在2021年12月期间,我们分析了ZabBiX Web前端的外部攻击表面,以更好地理解与该软件暴露于不可信网络相关的风险。这一努力导致发现了两个关键漏洞:CVE-2022-23131和CVE-2022-23134。
这些发现都与Zabbix在客户端存储会话数据的方式有关。我们将指导您完成其脆弱的实现,讨论其影响,以及在早期开发阶段如何发现它。
发现的漏洞影响到我们研究时支持的所有Zabbix Web前端版本,包括5.4.8、5.0.18和4.0.36。它们不需要事先了解目标,攻击者可以毫不费力地实现自动化。
我们强烈建议将运行Zabbix Web前端的实例升级到6.0.0beta2、5.4.9、5.0.19或4.0.37,以保护您的基础架构。
在启用SAML SSO身份验证的情况下,它允许绕过身份验证并获得管理员权限。攻击者可利用该访问权限在链接的Zabbix服务器和带有CVE-2021-46088的Zabbix代理实例上执行任意命令,其攻击代码已经公开。与Zabbix代理不同,无法将Zabbix服务器配置为不允许执行命令。
服务器端会话是PHP的内置功能。客户端在cookie中被分配了一个唯一的会话标识符,PHPSESSID是最常见的会话标识符,并且必须在每个请求中传输它。在服务器端,PHP获取这个值,并在文件系统(/var/lib/PHP/sessions,有时是/tmp/)上查找相关的会话值,以填充超全局变量$_session。客户端不能自由修改会话值,因为它们只控制会话的标识符。
Zabbix Web前端基于PHP的强大功能、自定义会话处理程序推出了自己的客户端存储实现。通过使用实现SessionHandlerInterface的类调用session_set_save_handler(),对$_session的所有后续访问都将由此类的方法处理。
在他们的例子中,目标是将对$_会话的任何访问映射到cookie。例如,索引$_SESSION会导致调用CCookieSession::read();CCookieHelper::get()只是$_COOKIE的包装器:
<;?php类CCookiSession实现SessionHandlerInterface{/[…]public const COOKIE_NAME=ZBX_SESSION_NAME;//[...] 公共函数read($session_id){$session_data=json_decode($this->;parseData(),true);//[…]foreach($key=>;$value){csSessionHelper::set($key,$value);}//[...] 受保护函数parseData():字符串{if(ccookieheloper::has(self::COOKIE_NAME)){return base64_decode(ccookieheloper::get(self::COOKIE_NAME));}返回''; }
Zabbix开发人员引入了一种方法来验证存储在cookie中的数据,并确保它们不被篡改。此功能在CEncryptedCookieSession中实现:
类CEncryptedCookieSession扩展了CCookieSession{/[…]公共函数extractSessionId():?字符串{/[…]if(!$this->;checkSign($session_data)){return null;}//[...] 返回$session_data[';sessionid';]}/[...] 受保护函数checkSign(string$data):bool{$data=json_decode($data,true);如果(!is_array($data)| |!array_key_存在(';sign';,$data)){返回false;}$session_sign=$data[';sign';];未设置($data[';sign';])$sign=CEncryptHelper::sign(json_encode($data));返回$session_sign&&$标志及&;CEncryptHelper::checkSign($session_sign,$sign);}
作为高级读者的旁注,这里有一个巨大的危险信号:“签名”和“加密”可以互换使用。CEncryptHelper::sign()在内部使用AES ECB,易于延展,无法提供有关数据真实性的安全保证。这种结构的使用还带来了另一个安全建议,但本文将不详细介绍。
方法CEncryptedCookieSession::checkSign()仅在CEncryptedCookieSession::extractSessionId()中调用,但在CCookieSession方法中(例如,在中的访问期间)从不调用 CCookieSession::read())。当访问sessionid以外的字段时,永远不会验证会话的真实性。
由于cookie完全由客户端控制,因此它们基本上可以控制会话。这是非常罕见的,并且打破了大多数关于存储在其中的值的可信度的假设。它可能会导致使用会话的应用程序部分存在漏洞。
安全断言标记语言(SAML)是最常见的单点登录(SSO)标准之一。它围绕XML实现,允许身份提供者(IdP,一个能够验证用户身份的实体)告诉服务提供者(SP,这里是Zabbix)你是谁。您可以将Zabbix Web前端配置为允许用户通过SAML进行身份验证,但默认情况下不会启用,因为它需要了解身份提供者的详细信息。这是企业部署最常见的设置。
与SAML身份验证机制相关的代码可以在index_sso中找到。php。简而言之,其目标是:
用户经过身份验证后,验证传入SAML负载的格式和签名。创建一个名为saml_data的会话条目来记住用户';个性;
如果会话中存在名为saml_data的条目,则提取其值,并根据username_属性的值在Zabbix上对用户进行身份验证。
如前一节所述,此文件中从未调用CEncryptedCookieSession::checkSign(),因此会话条目saml_data[用户名_属性]的值可以完全由客户端控制:
这种攻击非常简单,特别是因为Zabbix Web前端自动配置了名为Admin的高权限用户。
一旦在仪表板上验证为管理员,攻击者可以在任何连接的Zabbix服务器上执行任意命令,如果AllowKey=system的配置中明确允许,攻击者可以在Zabbix代理上执行任意命令。运行[*](非默认)。
在安装程序中发现了另一次不安全使用会话的情况。php。此脚本通常由系统管理员在首次部署Zabbix Web前端时运行,以后仅允许经过身份验证的高权限用户访问。
此页面通常使用会话来跟踪整个设置步骤的进度;同样,这里从未调用CEncryptedCookieSession::checkSign()。在将entry step设置为6的情况下创建会话,可以重新运行安装过程的最新步骤。
这一步对攻击者来说非常有趣,因为它的目标是创建Zabbix Web前端配置文件conf/Zabbix。conf.php:
私有函数stage6():数组{/[…][1] $config=new CConfigFile($config\u file\u name)$配置->;config=[';DB';=>;[';TYPE';=>;$this->;getConfig(';DB#u TYPE';)'服务器'=>$这个->;getConfig(';DB#U服务器';)'39号港口=>$这个->;getConfig(';DB#U端口';)'数据库'=>$这个->;getConfig(';DB#U数据库';),//[...] ] + $db_creds_config+$vault_config,//[…]$error=false;//[...] [2] $db_connect=$this->;dbConnect($db_user,$db_pass)$is_superadmin=(CWebUser::$data&;CWebUser::getType()==USER_TYPE_SUPER_ADMIN)$session_key_update_failed=($db_connect&;!$is_superadmin)!CEncryptHelper::updateKey(CEncryptHelper::generateKey()):false;如果(!$db_connect | |$session_key_update_失败){/[…]退回$this->;stage2();}//[...] 如果(!$config->;save()){/[…]
在[1]中,将创建一个新的CConfigFile对象来存储和验证新的配置值。方法CSetupWizard::getConfig()只是当前会话的包装器,因此这些值完全由攻击者控制。
在[2]处,代码试图通过尝试连接到新数据库配置来确定其是否有效。由于该代码只应在初始设置过程中调用,当用户帐户和数据库设置(如加密密钥)尚未设置时,控制会话的攻击者将能够通过各种检查。
因此,即使Zabbix Web前端实例已经处于工作状态,攻击者也可以覆盖现有的配置文件。通过指向其控制下的数据库,攻击者可以使用高度特权的帐户访问仪表板:
重要的是要理解,这种访问不能用于访问部署在网络上的Zabbix代理:Zabbix Web前端和Zabbix服务器都必须使用相同的数据库才能进行通信。仍有可能将其与web仪表板上的代码执行漏洞相关联,以获得对数据库的控制,并在网络上转向。
在未加固或旧环境中,也可能存在其他利用场景。例如,PHP的MySQL客户端实现了LOAD DATA LOCAL语句,但现在默认情况下禁用了3年。另一个原因可能是,在验证数据库配置时,存在对带有完全受控参数的文件_exists()的调用,这是一个已知的安全风险,因为存在潜在的危险方案包装,如phar://。
SSO身份验证流中引入了一个附加的签名字段,以防止用户更改存储在会话(0395828a)中的SAML属性
会话cookie的身份验证方式现在使用HMAC构造而不是AES ECB(eea1f70a)完成。
如果实例已安装且当前用户没有超级管理员角色(20943ae3),安装过程现在会提前退出。
他们还决定不强制执行cookie签名检查:这种方法的主要缺点是,如果忘记了对CEncryptedCookieSession::checkSign()的调用,依赖会话的新功能可能会引入类似的漏洞。也没有办法检测潜在的安全倒退。
在本文中,我们介绍了在实现客户端会话存储时常见的安全问题。作为一个案例研究,我们描述了在流行的开源监控平台Zabbix中发现的高严重性漏洞。具有相同根源的漏洞CVE-2022-23131和CVE-2022-23134可导致绕过身份验证,并使远程攻击者能够在目标服务器实例上执行任意代码。
在编写和审查与重要安全功能相关的代码时,很容易做出与引入该漏洞的原始开发人员相同的假设。在这里,没有任何与客户端会话存储相关的集成测试能够发现这种行为。
始终通过VPN或一组受限IP地址的扩展内部访问(如编排、监控)提供对合理服务的访问,强化文件系统权限以防止意外更改,删除安装脚本等。
我们要感谢Zabbix维护人员的响应能力和强大的披露流程。