Visionaries Have Democratised Remote Network Access – Citrix Virtual Apps and Desktops (CVE Unknown)
Well, we’re back again, with yet another fresh-off-the-press bug chain (and associated Interactive Artifact Generator). This time, it’s in Citrix’s “Virtual Apps and Desktops” offering.
好吧,我们又回来了,带来了另一个新鲜的 bug 链(以及相关的 Interactive Artifact Generator)。这一次,它出现在 Citrix 的 “Virtual Apps and Desktops” 产品中。
This is a tech stack that enables end-users (and likely, your friendly neighbourhood ransomware gang) to access their full desktop environment from just about anywhere, whether they’re using a laptop, tablet, or even a phone.
这是一个技术堆栈,使最终用户(可能还有您友好的邻居勒索软件团伙)能够从几乎任何地方访问其完整的桌面环境,无论他们使用的是笔记本电脑、平板电脑还是手机。
It’s essentially the ‘thin client’ experience that people were very excited about some 30 years ago – instead of having software and files stored on each individual device, the application (or entire desktop) runs on a big meaty server safely tucked away in a datacenter, and is streamed to end-users via the network. It’s sort of like Remote Desktop, but more enterprise-y.
它本质上是大约 30 年前人们非常兴奋的“瘦客户端”体验 – 应用程序(或整个桌面)不是将软件和文件存储在每个单独的设备上,而是在安全地隐藏在数据中心的大型服务器上运行,并通过网络流式传输给最终用户。它有点像远程桌面,但更像企业。
It is, of course, a bit more polished than Remote Desktop, fully capable of being used by an end user. Here’s an example (courtesy of Citrix) of a user running Chrome via their organization’s setup.
当然,它比远程桌面更精致一些,完全能够被最终用户使用。以下是用户通过其组织的设置运行 Chrome 的示例(由 Citrix 提供)。
Generally speaking, enterprises love this kind of tech. This is for a few different reasons:
一般来说,企业喜欢这种技术。这有几个不同的原因:
- Full Desktop Experience: When done right, users will ‘feel’ like they’re using their own desktop, with all their usual apps, files, and settings. Oftentimes, users can’t even tell that there’s an entire network between them and their application, but in reality, it’s all hosted somewhere else. This means that users can pick up right where they left off, no matter what device they are on.
完整的桌面体验:如果操作得当,用户将“感觉”就像他们正在使用自己的桌面,其中包含他们所有常用的应用程序、文件和设置。通常,用户甚至无法分辨出他们和他们的应用程序之间有一个完整的网络,但实际上,它都托管在其他地方。这意味着用户无论使用什么设备,都可以从上次中断的地方继续。 - Easy Management: Virtual desktops are a dream for IT teams to manage (again with the caveat of ‘when done right’). Administrators can update, secure, and troubleshoot from one central location rather than on every employee’s device. Imagine a world where the only things you have to troubleshoot outside the server room are keyboards, mice, and monitors. That’s the promise.
易于管理:虚拟桌面是 IT 团队的梦想管理(同样需要注意“如果做得正确”)。管理员可以从一个中心位置进行更新、保护和故障排除,而不是在每个员工的设备上进行更新。想象一下这样一个世界:在服务器机房之外,您唯一需要排除故障的是键盘、鼠标和显示器。这就是承诺。 - ✨✨Enhanced Security✨✨ (…………………………): Since data is stored in a central location, it’s easier to keep secure, or so the theory goes. If someone loses their device, there’s no data on it; it’s all safely stored in the cloud. You don’t have to revoke anything or remotely wipe any devices.
✨✨增强的安全性✨✨ (…………………………):由于数据存储在中央位置,因此更容易保持安全,或者理论上是这样。如果有人丢失了他们的设备,则设备上没有数据;它们都安全地存储在云中。您不必撤销任何内容或远程擦除任何设备。 - Remote Work Ready: Users can log in to their desktop from home, the office, or anywhere else they can get to the network, which is ideal for flexible work arrangements and keeps everything consistent.
远程工作就绪: 用户可以在家中、办公室或任何可以访问网络的地方登录他们的桌面,这是灵活的工作安排的理想选择,并保持一切一致。
Without sounding like a Citrix salesperson, Virtual Desktops gives users a seamless desktop experience wherever they are, and companies get easier management and something about security benefits, we assume.
我们假设,Virtual Desktops 听起来不像 Citrix 销售人员,但无论用户身在何处,它都能为用户提供无缝的桌面体验,并且公司可以更轻松地进行管理并获得一些安全优势。
Attackers, of course, also love this technology, but for a very different reason.
当然,攻击者也喜欢这项技术,但原因截然不同。
Having your enterprise applications published interactively and ready for your users can also mean having them ready for attackers, just one authentication step away – and we’ve certainly seen that attackers have enjoyed some great successes, with CVE-2024-6151 being the most recent example.
以交互方式发布您的企业应用程序并为您的用户做好准备,也可能意味着它们为攻击者做好准备,只需一个身份验证步骤即可 – 我们当然已经看到攻击者取得了一些巨大的成功,CVE-2024-6151 就是最新的例子。
This one is a privesc bug yielding SYSTEM privileges for any VDI user, which is actually a lot worse than it might initially sound since that’s SYSTEM privileges on the server that hosts all the applications and access is ‘by design’ – allowing an attacker to impersonate any user (including administrators) and monitor behaviour, connectivity.
这是一个为任何 VDI 用户产生 SYSTEM 权限的特权错误,这实际上比最初听起来要糟糕得多,因为这是托管所有应用程序的服务器上的 SYSTEM 权限,并且访问是“设计使然”——允许攻击者冒充任何用户(包括管理员)并监控行为、连接性。
Since everything is so seamless and portable, it’s an easy jump from there to impersonating users or ‘shadowing’ them, observing their every action. The centralized administration system can easily become a panopticon.
由于一切都是如此无缝和可移植,因此从那里很容易跳到冒充用户或“跟踪”他们,观察他们的一举一动。集中式管理系统很容易成为一个 panopticon。
However, we haven’t seen many true unauthenticated RCE bugs reported.
但是,我们尚未看到许多真正的未经身份验证的 RCE 错误报告。
We came up with 6 potential reasons:
我们提出了 6 个潜在原因:
- Perhaps this is due to the architecture of thin-client solutions, in which privilege escalation attacks are so powerful.
也许这是由于瘦客户端解决方案的架构造成的,其中权限提升攻击非常强大。 - Perhaps these solutions were built in a way that reflected the necessary security posture of a remote desktop access tool for enterprises?
也许这些解决方案的构建方式反映了企业远程桌面访问工具的必要安全状况? - Perhaps these solutions were built in a way that reflected the necessary security posture of a remote desktop access tool for enterprises?
也许这些解决方案的构建方式反映了企业远程桌面访问工具的必要安全状况? - Perhaps these solutions were built in a way that reflected the necessary security posture of a remote desktop access tool for enterprises?
也许这些解决方案的构建方式反映了企业远程桌面访问工具的必要安全状况? - Perhaps these solutions were built in a way that reflected the necessary security posture of a remote desktop access tool for enterprises?
也许这些解决方案的构建方式反映了企业远程桌面访问工具的必要安全状况? - Perhaps these solutions were built in a way that reflected the necessary security posture of a remote desktop access tool for enterprises?
也许这些解决方案的构建方式反映了企业远程桌面访问工具的必要安全状况?
Regardless, Citrix’s Virtual Apps and Desktop solution is a huge, complex system with many moving parts (we’ll zoom in on just one of them and tear it to pieces shortly).
无论如何,Citrix 的 Virtual Apps and Desktops 解决方案是一个庞大而复杂的系统,包含许多活动部件(我们只放大其中一个,并很快将其撕成碎片)。
Everyone knows the more complex things get, the more chance of bugs, and while the vendor puts a lot of effort into securing it [citation needed] , there must be some RCE out there? Surely?
每个人都知道事情越复杂,出现错误的机会就越大,虽然供应商投入了大量精力来保护它 [需要引用] ,但肯定有一些 RCE 吗?肯定?
Given the historical lack of weaknesses demonstrating an apparent lack of world-ending destruction, we decided to take a closer look at the product.
鉴于历史上缺乏弱点,表明明显缺乏世界末日的破坏,我们决定仔细研究一下这个产品。
Did Citrix democratise remote network access? Removing this privilege from the authorised?
Citrix 是否使远程网络访问大众化?从授权中删除此权限?
Citrix Session Recording – AKA ‘the source of all evil’
Citrix Session Recording – 又名“万恶之源”
One thing that a ‘thin client’-esque solution lends itself to very well is monitoring – also known as ‘stalking’, ‘audit functions’, or just ‘the ability for administrators to see what a logged-in user is doing’.
“瘦客户端”式解决方案非常适合的一件事是监控 – 也称为“跟踪”、“审计功能”,或者只是“管理员查看登录用户正在做什么的能力”。
In the context of a VDI solution, the session data being sent to the client is literally a video stream. Thus, it’s “easy” for an authorized administrator to tee off a feed and watch for themselves.
在 VDI 解决方案的上下文中,发送到客户端的会话数据实际上是一个视频流。因此,授权管理员 “很容易” 开球并自行监视。
Citrix have taken this a little bit further with their feature “Session Recording”. This feature captures user activity, recording keyboard and mouse input, along with the video stream of the desktop’s reaction. It’s something akin to recording of a virtual machine session (or, your APT friend spamming their Cobalt Strike beacon with the ‘screenshot’ command).
Citrix 通过其功能“Session Recording”更进一步。此功能捕获用户活动,记录键盘和鼠标输入,以及桌面反应的视频流。这类似于虚拟机会话的录制(或者,您的 APT 朋友使用“screenshot”命令向他们的 Cobalt Strike 信标发送垃圾邮件)。
Citrix advertise the feature as being really useful for monitoring (somewhat obviously), but also for compliance and troubleshooting. It can even be set up so that certain actions (like identifying sensitive data) will trigger recording, which helps meet regulatory needs and flag suspicious activities.
Citrix 宣传该功能对于监控(有点明显)非常有用,但对于合规性和故障排除也非常有用。它甚至可以进行设置,以便某些操作(如识别敏感数据)触发记录,这有助于满足监管需求并标记可疑活动。
It’s also invaluable for troubleshooting, as end-users can ‘record’ a problem manifesting, showing the technical team exactly what happens (rather than just opening a ticket with the subject of “it doesn’t work”).
它对于故障排除也很有价值,因为最终用户可以“记录”问题的表现,向技术团队展示具体发生的事情(而不仅仅是以“它不起作用”为主题打开一个工单)。
Here’s an example of what a user sees when their session is recorded:
以下是用户在录制会话时看到的内容示例:
Overall, it provides a secure record of user activity, helping with audits, detecting unusual behavior, and diagnosing problems. Now, here is what a session recording flow looks like from a user’s perspective:
总体而言,它提供了用户活动的安全记录,有助于审计、检测异常行为和诊断问题。现在,从用户的角度来看,以下是会话录制流程的样子:
User Login
|
v
+-------------------+
| Citrix Login |
+-------------------+
|
v
+------------------------+
| Virtual Apps and |
| Desktops Environment |
+------------------------+
|
v
+------------------------+
| Start Session |
+------------------------+
|
v
+------------------------------------+
| Session Recording Service |
| - Monitors the session |
| - Starts recording if conditions |
| are met (e.g., policy triggered) |
+------------------------------------+
|
v
+------------------------------------+
| Record User Actions |
| - Keystrokes, application access, |
| and screen activity |
+------------------------------------+
|
v
+------------------------------------+
| Store Session Recording |
| - Saved in a secure repository |
| - Available for review by admins |
+------------------------------------+
One of our key motivations was to uncover the architecture behind this feature—how Citrix engineers approached recording a user’s session, handling the data securely, and transmitting it within the environment. This isn’t just a matter of “starting a screen recorder.”
我们的主要动机之一是揭示此功能背后的架构 — Citrix 工程师如何记录用户的会话、安全地处理数据并在环境中传输数据。这不仅仅是 “启动屏幕录像机” 的问题。
Questions came to mind: 问题浮现在脑海中:
- Which process captures the session?
哪个进程捕获会话? - How is the recording stored?
录音如何存储? - Are multiple components involved, or does a single process oversee the entire operation?
是否涉及多个组件,还是单个过程监督整个操作?
The answers to these questions map out a critical attack surface, which is exactly what makes this feature so interesting from a security perspective.
这些问题的答案描绘了一个关键的攻击面,从安全角度来看,这正是此功能如此有趣的原因。
Let’s be clear: recording user sessions reliably, and at scale, as Citrix does, is extremely challenging.
让我们明确一点:像 Citrix 一样可靠、大规模地记录用户会话极具挑战性。
This isn’t like opening QuickTime and pressing “record” on a laptop; this is a web application that’s streaming and recording multiple remote desktop sessions simultaneously in a secure, enterprise-grade product.
这不像在笔记本电脑上打开 QuickTime 并按下“录制”;这是一个 Web 应用程序,可在安全的企业级产品中同时流式传输和录制多个远程桌面会话。
The sheer technical demand, combined with the complexity of coordinating processes that handle real-time streaming, storage, and secure data transfer, means there are countless intricate moving parts. And as we know, complexity often breeds opportunities for mistakes—mistakes that sometimes lead to serious vulnerabilities.
纯粹的技术需求,加上处理实时流、存储和安全数据传输的协调流程的复杂性,意味着有无数错综复杂的活动部件。正如我们所知,复杂性往往会滋生出错的机会,这些错误有时会导致严重的漏洞。
We wanted to peel back the layers of this sophisticated feature, bringing awareness to the complexities Citrix engineers navigate and why such features deserve rigorous scrutiny. Understanding these details underscores the impressive scope of Citrix’s technology, while also illuminating the potential for security gaps within a powerful but complex system.
我们希望揭开这一复杂功能的层层,让人们意识到 Citrix 工程师所经历的复杂性以及为什么这些功能值得严格审查。了解这些细节强调了 Citrix 技术令人印象深刻的范围,同时也揭示了强大但复杂的系统中可能存在的安全漏洞。
Eventually, after reviewing processes and examining the documentation, we came up with the following diagram.
最终,在审查了流程并检查了文档之后,我们得出了下图。
Citrix Session Recording Conceptual Architecture
+-------------+ +---------+ +----------------------------+
| User | -----> | Citrix | -----> | Virtual Desktops |
+-------------+ +---------+ | and Servers |
| |
| +------------------------+ |
| | Windows 10/11 VDA | |
| +------------------------+ |
+----------------------------+
|
v
+----------------------------------+
| Session Recording Server |
+----------------------------------+
| | |
v v v
+------------------+ +----------------+ +-----------+
| Recording Policy | | Recording | | Database |
| Console | | Player | +-----------+
+------------------+ +----------------+
As you can see, there is a “Session Recording Server”, which can be on the same machine where Citrix Virtual Apps and Desktop is installed or on a separate machine.
如您所见,有一个“Session Recording Server”,它可以位于安装了 Citrix Virtual Apps and Desktops 的同一台计算机上,也可以位于单独的计算机上。
When the Citrix main component records the session, it passes it to this ‘Session Recording Server’, which then stores the recording in a database, along with the metadata you’d expect—the user who submitted it, the date, and suchlike.
当 Citrix 主组件录制会话时,它会将其传递给此“Session Recording Server”,然后该服务器将录制内容以及您所需的元数据(提交该会话的用户、日期等)存储在数据库中。
At a later date, an authorized administrator can then examine the session footage using the Player component, which queries the database attached to the Session Recording Server and retrieves the relevant information.
然后,授权管理员可以使用播放器组件检查会话素材,该组件会查询附加到 Session Recording Server 的数据库并检索相关信息。
Finally, the ‘Recording Policy Console’ is the component that exposes more fine-grained control to administrators, allowing specific triggers to be set for when the session should start recording. For example, you could start a recording session every time a user accessed a particularly high-value file share.
最后,“录制策略控制台”是向管理员提供更精细控制的组件,允许为会话何时开始录制设置特定触发器。例如,您可以在每次用户访问特别高价值的文件共享时启动录制会话。
Okay so now we understand the roles of these different components, but one question remains: how do these components communicate with each other? Is the communication via network sockets? Maybe named pipes, or some kind of shared memory?
好了,现在我们了解了这些不同组件的作用,但仍然存在一个问题:这些组件如何相互通信?是否通过网络套接字进行通信?也许是命名管道,或者某种共享内存?
To answer these questions, it’s time to dig around in the filesystem and find the executables involved. Fortunately, this is straightforward, as Citrix helpfully keeps an organized folder structure. We soon found a directory named ‘SessionRecording’ – what could it hold but components relating to session recording?
要回答这些问题,是时候在文件系统中四处挖掘并找到所涉及的可执行文件了。幸运的是,这很简单,因为 Citrix 有助于保持井井有条的文件夹结构。我们很快就找到了一个名为“SessionRecording”的目录 – 除了与会话录制相关的组件之外,它还能容纳什么呢?
Perfect. We’ve got a bunch of folders here for the various components of session recording – the database component, for example, the player, and (most importantly!) the ‘Server’ component, where all the juicy logic is likely to be tucked away waiting for us.
完善。我们这里有一堆文件夹,用于会话录制的各个组件 – 数据库组件,例如播放器,以及(最重要的是)“服务器”组件,所有有趣的逻辑都可能被隐藏起来等待我们。
We started to take a closer look at some of the components involved, looking for any sign of communication with other components. While doing so, we stumbled upon an executable named “SsRecStorageManager.exe” – which, intriguingly, was running by default as a Windows service.
我们开始仔细研究一些涉及的组件,寻找与其他组件通信的任何迹象。在此过程中,我们偶然发现了一个名为“SsRecStorageManager.exe”的可执行文件 – 有趣的是,它默认作为 Windows 服务运行。
With a filename like that, this must be a component that handles the storage of session recordings. And the icon of a video camera is just too tempting to ignore – what hacker doesn’t want to be able to spy on sessions like a video camera?!
使用这样的文件名,这必须是处理会话录制件存储的组件。摄像机的图标太诱人了,不容忽视 – 哪个黑客不想像摄像机一样监视会话?!
Of course, being the smart hackers we are, our first port of call is simply to read the documentation for this component (rather than dive right in with a decompiler). We did a quick search and found the following piece of documentation:
当然,作为聪明的黑客,我们的第一站就是阅读这个组件的文档(而不是直接使用反编译器)。我们进行了快速搜索并找到了以下文档:
Citrix Session Recording Storage Manager is a Windows service that manages the recorded session files received from each Session Recording-enabled computer running XenApp and XenDesktop. The Storage Manager receives the session recordings as message bytes via the Microsoft Message Queuing (MSMQ) service. To maintain the integrity of the recordings at all times, the Storage Manager should be able to manage the received messages as quickly as they are sent by the Session Recording agent.
Citrix Session Recording Storage Manager 是一项 Windows 服务,用于管理从每台运行 XenApp 和 XenDesktop 的支持 Session Recording 的计算机接收的录制的会话文件。Storage Manager 通过 Microsoft 消息队列 (MSMQ) 服务以消息字节的形式接收会话记录。为了始终保持录制内容的完整性,存储管理员应能够在 Session Recording Agent 发送收到的消息时尽快对其进行管理。
Okay, great – now we have a general understanding of what this service does. It takes the recorded session files, receiving them via MSMQ. This component, MSMQ, or ‘Microsoft Message Queuing’, simply allows two separate processes to communicate via a ‘queue’ – for example, one side might enqueue a message along the lines of ‘list all the recordings in the database’, and then another application might pick up this message from the queue and respond by placing a list of recording data back into the queue.
好了,太好了 – 现在我们对这项服务的作用有了大致的了解。它获取录制的会话文件,并通过 MSMQ 接收它们。此组件 MSMQ 或“Microsoft 消息队列”仅允许两个单独的进程通过“队列”进行通信 – 例如,一方可能按照“列出数据库中的所有记录”的顺序将消息排队,然后另一个应用程序可能会从队列中获取此消息,并通过将记录数据列表放回队列来响应。
There’s an important detail that this process implies, however.
但是,此过程暗示了一个重要的细节。
Because the queue deals with data that travels between processes (and even between entire machines), some kind of conversion is needed for the objects placed in the queue. We can’t simply dump chunks of memory in there, since they might not be understood by the receiving end – we need some kind of Serialization process to convert the data into a form that can then be interpreted by the other end. (for those .NET enthusiasts, .NET usually refers to this as ‘Marshalling’).
由于队列处理在进程之间(甚至在整个计算机之间)传输的数据,因此需要对队列中的对象进行某种转换。我们不能简单地将内存块转储到其中,因为它们可能无法被接收端理解 – 我们需要某种序列化过程将数据转换为可以被另一端解释的形式。(对于那些 .NET 爱好者,.NET 通常将其称为“编组”)。
Of course, complexity is a great place for bugs to hide, and historically, serialization interfaces have proven a great way for attackers to surreptitiously insert their own data, which the trusting application will then deserialize and process as if it originated from a trusted party. There’s a minor detail here, in that the MSMQ component isn’t actually exposed to the network via TCP, but don’t fret – we will deal with that part later.
当然,复杂性是隐藏 bug 的好地方,从历史上看,序列化接口已被证明是攻击者秘密插入自己的数据的好方法,然后受信任的应用程序将反序列化和处理这些数据,就像它来自受信任的一方一样。这里有一个小细节,因为 MSMQ 组件实际上并没有通过 TCP 向网络公开,但不要担心 – 我们稍后会处理这部分。
Casting our gaze back over the documentation, it is stated that session recording data is transferred simply as ‘message bytes’. Just reading this triggered our ‘spidey sense’ and made us want to learn more about how these ‘message bytes’ are transferred – how are they serialized, and are we able to abuse the deserialization process? So, we broke out our trust decompiler and started dissecting the service. For those following along, we performed this analysis on version Citrix_Virtual_Apps_and_Desktops_7_2402_LTSR
.
将目光投向文档,其中指出会话记录数据仅作为“消息字节”传输。光是读到这里就触发了我们的 “蜘蛛侠感”,让我们想更多地了解这些 “消息字节” 是如何传输的 – 它们是如何序列化的,我们是否能够滥用反序列化过程?因此,我们打破了我们的信任反编译器并开始剖析该服务。对于后续用户,我们对版本 Citrix_Virtual_Apps_and_Desktops_7_2402_LTSR
进行了此分析。
Reviewing a codebase of this size is always a time-consuming task, and patience is required as we exhaustively audit code. Eventually, though, we came across a class named SmAudStorageManager.EventMetadataWithTime
, which stood out to us. Take a look:
审查这种规模的代码库始终是一项耗时的任务,在我们详尽地审计代码时需要耐心。不过,最终,我们遇到了一个名为 SmAudStorageManager.EventMetadataWithTime
的类,它对我们来说非常突出。看一看:
/* 1 */ using System;
/* 2 */ using SmAudCommon;
/* 3 */
/* 4 */ namespace SmAudStorageManager
/* 5 */ {
/* 6 */ // Token: 0x0200000A RID: 10
/* 7 */ [Serializable]
/* 8 */ internal class EventMetadataWithTime
/* 9 */ {
/* 10 */ // Token: 0x04000048 RID: 72
/* 11 */ public EventMetadata m_eventMetadata;
/* 12 */
/* 13 */ // Token: 0x04000049 RID: 73
/* 14 */ public DateTime m_eventTime;
/* 15 */
/* 16 */ // Token: 0x0400004A RID: 74
/* 17 */ public DateTime m_eventUtcTime;
/* 18 */
/* 19 */ // Token: 0x0400004B RID: 75
/* 20 */ public string m_user;
/* 21 */
/* 22 */ // Token: 0x0400004C RID: 76
/* 23 */ public string m_domain;
/* 24 */
/* 25 */ // Token: 0x0400004D RID: 77
/* 26 */ public string m_server;
/* 27 */
/* 28 */ // Token: 0x0400004E RID: 78
/* 29 */ public Guid m_ctxSessionID;
/* 30 */ }
/* 31 */ }
One can immediately notice this class has been marked with the [Serializable]
attribute, which raises our suspicion that there might be some sort of de/serialization being performed by this executable.
人们可以立即注意到这个类已经被标记为 [Serializable]
属性,这让我们怀疑这个可执行文件可能正在执行某种解/序列化。
Indeed, the .NET documentation states simply that this attribute “Indicates that a class can be serialized using binary or XML serialization”. It seems very likely that this executable is serializing data ready for the MSMQ queue.
事实上,.NET 文档仅指出此属性“指示可以使用二进制或 XML 序列化来序列化类”。此可执行文件很可能正在序列化数据,以便为 MSMQ 队列做好准备。
From here, we followed the footprints of serialization usage across the codebase, decompiling more libraries to find what serialization API is being used, ultimately intending to check if serialization is being used in an insecure fashion.
从这里开始,我们跟踪了整个代码库中序列化使用情况的足迹,反编译了更多库以查找正在使用的序列化 API,最终打算检查序列化是否以不安全的方式使用。
After looking at countless different methods, we encountered the SmAudStorageManager.ProjectInstaller.Install(IDictionary)
method.
在研究了无数不同的方法之后,我们遇到了这种方法 SmAudStorageManager.ProjectInstaller.Install(IDictionary)
。
Let’s examine its implementation closely – pay attention to the code snippet down below while you’re following along.
让我们仔细检查一下它的实现 – 在继续学习时,请注意下面的代码片段。
You can notice that line (41) instantiates the MessageQueue
which is part of the MSMQ class System.Messaging.MessageQueue.MessageQueue
.
您可以注意到,第 (41) 行实例化了 MessageQueue
,它是 MSMQ 类 System.Messaging.MessageQueue.MessageQueue
的一部分。
Then, from line (45) to line (48), permission restrictions are placed on this queue instance by calling the SetPermissions
method.
然后,从第 (45) 行到第 (48) 行,通过调用 SetPermissions
方法对此队列实例施加权限限制。
public override void Install(IDictionary stateSaver)
{
/* 1 */ try
/* 2 */ {
/* 3 */ Trace.WriteLine(string.Format("\\nBegin Session Recording Storage Manager install @ {0} ...", DateTime.Now));
/* 4 */ Trace.WriteLine("Determining service dependencies...");
/* 5 */ ArrayList arrayList = new ArrayList();
/* 6 */ arrayList.Add("Eventlog");
/* 7 */ arrayList.Add("MSMQ");
/* 8 */ this.AddServiceNameIfExists(arrayList, "COMSysApp");
/* 9 */ this.AddServiceNameIfExists(arrayList, "EventSystem");
/* 10 */ this.serviceInstaller.ServicesDependedOn = (string[])arrayList.ToArray(typeof(string));
/* 11 */ Trace.WriteLine(" Service depends on:");
/* 12 */ foreach (string text in this.serviceInstaller.ServicesDependedOn)
/* 13 */ {
/* 14 */ Trace.WriteLine(string.Format(" {0}", text));
/* 15 */ }
/* 16 */ try
/* 17 */ {
/* 18 */ Trace.WriteLine("CitrixSsRecStorageManager: base.Install begin");
/* 19 */ this.UninstallIfExists("CitrixSsRecStorageManager");
/* 20 */ base.Install(stateSaver);
/* 21 */ Trace.WriteLine("CitrixSsRecStorageManager: base.Install end");
/* 22 */ }
/* 23 */ catch (Exception ex)
/* 24 */ {
/* 25 */ ExceptionHelper.TraceException(ex);
/* 26 */ throw;
/* 27 */ }
/* 28 */ try
/* 29 */ {
/* 30 */ ServiceSidController.AddServiceSid("CitrixSsRecStorageManager", ServiceSidController.SERVICE_SID_TYPE.SERVICE_SID_TYPE_UNRESTRICTED);
/* 31 */ }
/* 32 */ catch (Exception ex2)
/* 33 */ {
/* 34 */ ExceptionHelper.TraceException(ex2);
/* 35 */ throw;
/* 36 */ }
/* 37 */ try
/* 38 */ {
/* 39 */ Trace.WriteLine("Begin set MSMQ permissions...");
/* 40 */ Trace.WriteLine(string.Format(" Begin open queue {0}...", this.messageQueueInstaller.Path));
/* 41 */ MessageQueue messageQueue = new MessageQueue(this.messageQueueInstaller.Path);
/* 42 */ Trace.WriteLine(" End open queue");
/* 43 */ try
/* 44 */ {
/* 45 */ messageQueue.SetPermissions(ProjectInstaller.GetLocalizedTrusteeName(WellKnownSidType.BuiltinAdministratorsSid), MessageQueueAccessRights.FullControl, AccessControlEntryType.Allow);
/* 46 */ messageQueue.SetPermissions(ProjectInstaller.GetLocalizedTrusteeName(WellKnownSidType.LocalSystemSid), MessageQueueAccessRights.FullControl, AccessControlEntryType.Allow);
/* 47 */ messageQueue.SetPermissions(ProjectInstaller.GetLocalizedTrusteeName(WellKnownSidType.NetworkServiceSid), MessageQueueAccessRights.FullControl, AccessControlEntryType.Allow);
/* 48 */ messageQueue.SetPermissions(ProjectInstaller.GetLocalizedTrusteeName(WellKnownSidType.AnonymousSid), MessageQueueAccessRights.GenericWrite, AccessControlEntryType.Allow);
/* 49 */ Trace.WriteLine("End set MSMQ permissions");
/* 50 */ }
/* 51 */ catch (Exception ex3)
/* 52 */ {
/* 53 */ throw ex3;
/* 54 */ }
/* 55 */ finally
/* 56 */ {
/* 57 */ messageQueue.Close();
/* 58 */ }
/* 59 */ }
/* 60 */ catch (Exception ex4)
/* 61 */ {
/* 62 */ ExceptionHelper.TraceException(ex4);
/* 63 */ }
/* 64 */ try
/* 65 */ {
/* 66 */ string signingCertificateThumbprint = RegistryConfiguration.Server.SigningCertificateThumbprint;
/* 67 */ if (signingCertificateThumbprint != null && signingCertificateThumbprint != string.Empty)
/* 68 */ {
/* 69 */ CertSecurityHelper.AddCertAccessRuleThroughThumbprint(signingCertificateThumbprint, CertSecurityHelper.StorageManagerServiceUser);
/* 70 */ Trace.WriteLine(string.Format("Install AddCertAccessRule Finished.", Array.Empty<object>()));
/* 71 */ }
/* 72 */ }
/* 73 */ catch (Exception ex5)
/* 74 */ {
/* 75 */ this.SendToMsiLog(string.Format("Install AddCertAccessRule Exception: {0}.", ex5.Message));
/* 76 */ }
/* 77 */ try
/* 78 */ {
/* 79 */ DirFileSecurityHelper.UpdateRecordingDirAndFilePermission();
/* 80 */ }
/* 81 */ catch (Exception ex6)
/* 82 */ {
/* 83 */ this.SendToMsiLog(string.Format("Install UpdateRecordingDirAndFilePermission Exception: {0}.", ex6.Message));
/* 84 */ }
/* 85 */ try
/* 86 */ {
/* 87 */ ProjectInstaller.CheckDatabaseConnection();
/* 88 */ DirFileSecurityHelper.UpdateLiveRecordingFilePermission(DatabaseProxy.DatabaseConnection);
/* 89 */ }
/* 90 */ catch (Exception ex7)
/* 91 */ {
/* 92 */ this.SendToMsiLog(string.Format("Install UpdateLiveRecordingFilePermission Exception: {0}.", ex7.Message));
/* 93 */ }
/* 94 */ Trace.WriteLine("End Session Recording Storage Manager Install\\n");
/* 95 */ try
/* 96 */ {
/* 97 */ string text2 = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log");
/* 98 */ if (!Directory.Exists(text2))
/* 99 */ {
/* 100 */ DirectorySecurity directorySecurity = new DirectorySecurity();
/* 101 */ directorySecurity.AddAccessRule(new FileSystemAccessRule("NETWORK SERVICE", FileSystemRights.FullControl, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow));
/* 102 */ directorySecurity.AddAccessRule(new FileSystemAccessRule("Administrators", FileSystemRights.FullControl, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow));
/* 103 */ Directory.CreateDirectory(text2, directorySecurity);
/* 104 */ }
[..SNIP..]
One can quickly notice how terrible these permissions are, allowing the precious ‘Full Control’ access to almost everyone, and also GenericWrite
to AnonymousSid
at line (48), allowing anyone at all to put messages onto the queue, which will then be processed and acted upon.
人们可以很快注意到这些权限是多么可怕,它允许几乎所有人拥有宝贵的“完全控制”访问权限,并且在第 (48) 行向 AnonymousSid
发送 GenericWrite
权限,允许任何人将消息放入队列,然后对其进行处理和处理。
messageQueue.SetPermissions(ProjectInstaller.GetLocalizedTrusteeName(WellKnownSidType.AnonymousSid), MessageQueueAccessRights.GenericWrite, AccessControlEntryType.Allow);
So now, our picture of how this all works is getting clearer.
所以现在,我们对这一切如何运作的了解越来越清晰。
This method takes care of setting up the initial access to the MSMQ component, creating a queue, and setting its permissions. However, it sets permissions which are not restrictive enough. Some more analysis reveals that this queue is used, later on, to send and receive events that are of type EventMetadataWithTime
.
此方法负责设置对 MSMQ 组件的初始访问、创建队列和设置其权限。但是,它设置的权限限制不够。进一步的分析表明,此队列稍后用于发送和接收 EventMetadataWithTime
类型的事件。
Do you remember that we talked about this class earlier? Now we know where the [Serializable]
that we found earlier is being used – exactly here.
你还记得我们之前讲过这门课吗?现在我们知道之前找到的 [Serializable]
在哪里被使用 – 就在这里。
However – we still have unanswered questions:
但是 – 我们仍有未解答的问题:
- Where is this queue being used?
此队列在何处使用? - How is the data received from this queue processed?
如何处理从此队列接收的数据?
We looked at the rest of the methods, and found the OpenQueue()
method which answered most of our questions. Let’s have a look at it:
我们查看了其余方法,发现 OpenQueue()
方法回答了我们的大部分问题。让我们来看看它:
/* */ public bool OpenQueue()
/* */ {
/* 1 */ bool flag = false;
/* 2 */ try
/* 3 */ {
/* 4 */ string receiveQueueName = Globals.ReceiveQueueName;
/* 5 */ if (!MessageQueue.Exists(receiveQueueName))
/* 6 */ {
/* 7 */ throw new Exception(string.Format(Strings.Error_QueueDoesNotExist, receiveQueueName));
/* 8 */ }
/* 9 */ MessageQueue.EnableConnectionCache = true;
/* 10 */ this.m_DataQueue = new MessageQueue(receiveQueueName);
/* 11 */ if (!this.m_DataQueue.CanRead)
/* 12 */ {
/* 13 */ throw new Exception(string.Format(Strings.Error_QueueReadAccessDenied, receiveQueueName));
/* 14 */ }
/* 15 */ this.m_DataQueue.Formatter = new \
/* */ BinaryMessageFormatter(FormatterAssemblyStyle.Simple, \
/* */ FormatterTypeStyle.TypesWhenNeeded);
/* 16 */ Trace.WriteLine(string.Format("Open queue: {0}", receiveQueueName));
/* 17 */ flag = true;
/* 18 */ }
/* 19 */ catch (Exception ex)
/* 20 */ {
/* 21 */ string errorMethod_OpeningQueue = Strings.ErrorMethod_OpeningQueue;
/* 22 */ ExceptionHelper.LogException(ex, 2078, errorMethod_OpeningQueue, ExceptionHelper.LogAction.Error);
/* 23 */ }
/* 24 */ return flag;
/* 25 */ }
At line (4), a global variable named Globals.ReceiveQueueName
is retrieved. One can quickly guess that this is the message queue name. Then, at line (5), this queue name is checked for accessibility via the MessageQueue.Exists
method. After this, at line (10), this queue name is used to instantiate an instance of the MessageQueue
class, which is then assigned to the m_DataQueue
property.
在第 (4) 行,检索名为 Globals.ReceiveQueueName
的全局变量。人们可以很快猜到这是消息队列名称。然后,在第 (5) 行,通过 MessageQueue.Exists
方法检查此队列名称的可访问性。在此之后,在第 (10) 行,此队列名称用于实例化 MessageQueue
类的实例,然后将其分配给 m_DataQueue
属性。
What’s interesting here, though, is the fact that the m_DataQueue.Formatter
property for this queue is set at the line (15) to a BinaryMessageFormatter
.
不过,有趣的是,此队列的 m_DataQueue.Formatter
属性在第 (15) 行设置为 BinaryMessageFormatter
。
Ruh-Roh! 鲁鲁!
Time has told us that using a BinaryFormatter for deserialization is almost always dangerous – it exposes a lot of functionality to whoever can provide it with messages, and while it can be used securely, it provides enough ‘footguns’ that even Microsoft themselves say it shouldn’t be used:
时间告诉我们,使用 BinaryFormatter 进行反序列化几乎总是危险的 – 它向任何可以为其提供消息的人公开了大量功能,虽然它可以安全地使用,但它提供了足够的“脚枪”,甚至 Microsoft 自己都说它不应该使用:
The BinaryFormatter type is dangerous and is not recommended for data processing. Applications should stop using
BinaryFormatter
as soon as possible, even if they believe the data they’re processing to be trustworthy.BinaryFormatter
is insecure and can’t be made secure.
BinaryFormatter 类型是危险的,不建议用于数据处理。应用程序应尽快停止使用BinaryFormatter
,即使它们认为正在处理的数据值得信赖。BinaryFormatter
不安全,无法确保安全。
That’s some pretty strong language coming from the original creators of the library!
这是来自库的原始创建者的一些非常强烈的语言!
Connecting the pieces here, we can see that the Serializable
type that we saw earlier (EventMetadataWithTime
) is being deserialized using the BinaryFormatter assigned to the m_DataQueue
MSMQ instance. Let us not forget the fact that anyone can talk to this queue due to the insecure permissions that were set during the queue initialization routine.
连接此处的各个部分,我们可以看到,我们之前看到的 Serializable
类型 (EventMetadataWithTime
) 正在使用分配给 m_DataQueue
MSMQ 实例的 BinaryFormatter 进行反序列化。让我们不要忘记这样一个事实,即由于在队列初始化例程期间设置了不安全的权限,任何人都可以与此队列通信。
Before we dig too deep into Citrix, let’s take a quick step back and recap some BinaryFormatter theory.
在我们深入研究 Citrix 之前,让我们快速回顾一下 BinaryFormatter 理论。
.NET BinaryFormatter Deserialization 101
.NET BinaryFormatter 反序列化 101
We could talk for hours about exploiting .NET’s BinaryFormatter. There are so many gadgets and such a huge amount background knowledge which is always fun to outline and share. Unfortunately, though, time is always short, so we’ll stick with a quick refresher.
我们可以谈论几个小时的开发 .NET 的 BinaryFormatter 的 BinaryFormatter 中有这么多的小工具和如此大量的背景知识,概述和分享总是很有趣。但不幸的是,时间总是很短,所以我们还是要快速复习一下。
If you missed the Microsoft quote above, we reiterate it here:
如果您错过了上面的 Microsoft 引言,我们在这里重申一下:
BinaryFormatter
is insecure and can’t be made secure
BinaryFormatter
不安全,无法确保安全
Bear that in mind as we go forward (grepping your own codebase for BinaryFormatter and exploiting the results is left as an exercise for the reader).
在我们继续前进时,请记住这一点(为 BinaryFormatter 复制您自己的代码库并利用结果留给读者作为练习)。
So, what can go wrong when using a BinaryFormatter?
那么,使用 BinaryFormatter 时会出现什么问题呢?
A quick note at this point – please bear in mind here that the following is just an example to help the reader to quickly understand how a simple ‘gadget’ looks and that this isn’t the gadget we’ll use for our detection artefact generator exploitation.
在这一点上,请注意 – 请记住,以下示例只是一个示例,可帮助读者快速了解简单的“小工具”的外观,并且这不是我们将用于检测工件生成器开发的小工具。
Given the ability to persuade the target to deserialize a message we give it, the next step is to locate what’s known as a ‘gadget’. A ‘gadget’ is a class that contains one or more methods that are invoked during the deserialization process. Under controlled circumstances, these methods then do things useful to an attacker.
鉴于能够说服目标反序列化我们提供给它的消息,下一步是找到所谓的“小工具”。“gadget”是一个类,其中包含在反序列化过程中调用的一个或多个方法。在受控情况下,这些方法会对攻击者执行有用的操作。
This is easier to demonstrate than to explain, so take a look at the following code, which is of a dangerous type:
这比解释更容易,因此请看一下以下危险类型的代码:
using System;
using System.IO;
[Serializable]
public class LogFile
{
private string filePath;
public LogFile(string path)
{
filePath = path;
}
~LogFile()
{
if (File.Exists(filePath))
{
File.Delete(filePath);
Console.WriteLine($"[Warning] Deleted file: {filePath}");
}
}
}
}
This class contains a constructor that takes a string and assigns it to a property named “filePath.” It also contains a destructor that deletes the file when the object is destroyed. Innocent enough, right?
此类包含一个构造函数,该构造函数采用字符串并将其分配给名为 “filePath” 的属性。它还包含一个析构函数,用于在销毁对象时删除文件。够无辜的,对吧?
Ok, but what does this really mean in the context of exploitation? Well, if an attacker can deliver a serialized instance of this LogFile
class, the deserializer will duly instantiate the class, as one would expect. Once the garbage collector kicks in during the end of the object lifecycle, the destructor of the newly-deserialized class will be invoked, and the file deleted. Since the “filePath” field is set by the deserializer, it is under our control, and we can delete any file we like – pow, we’ve just discovered a ‘gadget’ that permits arbitrary file deletion.
好的,但这在剥削的背景下到底意味着什么?好吧,如果攻击者可以提供此 LogFile
类的序列化实例,则反序列化程序将适当地实例化该类,正如人们所期望的那样。在对象生命周期结束时,垃圾回收器启动后,将调用新反序列化类的析构函数,并删除该文件。由于 “filePath” 字段是由反序列化器设置的,因此它在我们的控制之下,我们可以删除任何我们喜欢的文件 – pow,我们刚刚发现了一个允许任意删除文件的 ‘gadget’。
Of course, this is just an example, and somewhat contrived. ‘Real’ gadgets that enable RCE are typically more complex, and the motivated reader is invited to explore the work of great security researchers (such as James Forshaw, Alvaro Muñoz, Oleksandr Mirosh, Soroush Dalili, Piotr Bazydło, to name a few). These individuals have developed ‘universal’ gadgets that can target specific .NET framework versions to achieve full remote code execution.
当然,这只是一个例子,而且有点做作。启用 RCE 的“真实”小工具通常更复杂,邀请有动力的读者探索伟大的安全研究人员(如 James Forshaw、Alvaro Muñoz、Oleksandr Mirosh、Soroush Dalili、Piotr Bazydło 等)的工作。这些人开发了“通用”小工具,可以针对特定的 .NET Framework 版本实现完全远程代码执行。
You can refer to the real-world gadgets reference above by taking a look at the YSoSerial.NET project.
您可以通过查看 YSoSerial.NET 项目来参考上面的实际 Gadgets 参考。
Exploiting MSMQ Deserialization
利用 MSMQ 反序列化
OK, so that’s interesting background knowledge, but how does it help us exploit MSMQ and thus our target itself?
好的,这是有趣的背景知识,但它如何帮助我们利用 MSMQ 从而利用我们的目标本身呢?
Well, armed with this knowledge, we know what to look for – some kind of class that exposes dangerous functionality after being deserialized.
好吧,有了这些知识,我们就知道要寻找什么 – 某种在反序列化后暴露危险功能的类。
Fortunately, we don’t need to find this ourselves – we’re going to use the TypeConfuseDelegate
gadget, which works against .NET framework targets. If you’d like to know more about this phenomenal gadget discovered by James Forshaw, refer to this article.
幸运的是,我们不需要自己找到它 – 我们将使用 TypeConfuseDelegate
小工具,它适用于 .NET 框架目标。如果您想了解更多关于 James Forshaw 发现的这个非凡小工具的信息,请参阅这篇文章。
We do, however, have one extra issue here, as we mentioned previously. MSMQ is usually reached via TCP port 1801, but this port isn’t open by default in a Citrix environment.
但是,正如我们之前提到的,我们在这里确实有一个额外的问题。MSMQ 通常通过 TCP 端口 1801 访问,但默认情况下,此端口在 Citrix 环境中未打开。
Naturally, at this point, we gave up and considered farming geese – how can we exploit our neat deserialization bug without access to the underlying service?
自然而然地,在这一点上,我们放弃了,并考虑了养鹅——我们如何在无法访问底层服务的情况下利用我们简洁的反序列化 bug?
After staring at the wall, jumping on some straight command injection vulnerabilities, and soul-searching – we remembered CVE-2023-21554, which was a bug in MSMQ itself.
在盯着墙,跳上一些直接的命令注入漏洞,并进行了自我反省之后,我们想起了 CVE-2023-21554,这是 MSMQ 本身的一个错误。
Since we’re the kind of people to read exploit code for fun, we recalled that the exploit code (handily found in Metasploit) appeared to contain an HTTP payload.
由于我们是那种以阅读漏洞利用代码为乐的人,我们记得漏洞利用代码(在 Metasploit 中很容易找到)似乎包含一个 HTTP 有效负载。
Could this mean there’s a way to ‘jump’ from HTTP into the MSMQ data?
这是否意味着有办法从 HTTP “跳转”到 MSMQ 数据?
Is it possible to enqueue a message entirely via HTTP?
是否可以完全通过 HTTP 将消息排入队列?
Citrix’s solution exposes HTTP/HTTPS to the Internet by default (duh), so perhaps there was some light?
Citrix 的解决方案默认将 HTTP/HTTPS 公开到 Internet(呃),所以也许有一些曙光?
A quick search yielded a promising result:
快速搜索产生了一个有希望的结果:
It seems that since MSMQ v3 (published a long time ago!), support for MSMQ over HTTP has been present. Woe betide us, though, it isn’t enabled by default.
似乎从 MSMQ v3(很久以前发布)开始,就已经出现了对 MSMQ over HTTP 的支持。然而,我们有祸了,它默认不是启用的。
Everyone knows that, for a secure product, the minimum of functionality should be exposed, right? Surely Citrix don’t enable this extra functionality, just because it’s there?
每个人都知道,对于安全的产品,应该公开最少的功能,对吧?Citrix 肯定不会仅仅因为它在那里就启用这个额外的功能吗?
I mean, why would they?
我的意思是,他们为什么会呢?
Their code uses MSMQ over TCP, surely they wouldn’t enable this unused feature and just gratuitously expose attack surface.. ?
他们的代码使用 MSMQ over TCP,他们肯定不会启用这个未使用的功能,而只是无端地暴露攻击面。?
…?
……?
………..?
……………..?
…………………….?
Exploiting MSMQ over HTTP!
利用 MSMQ over HTTP!
Well, as it turns out, they actually do enable this seemingly-unnecessary feature.
好吧,事实证明,他们确实启用了这个看似不必要的功能。
Even though they don’t use this feature within any functionality that we can see, it is nonetheless activated behind the scenes when a user installs the product.
尽管他们没有在我们可以看到的任何功能中使用此功能,但当用户安装产品时,它仍然会在幕后激活。
Perhaps they have a further product that uses it, which has to interface over HTTP.
也许他们有另一个使用它的产品,它必须通过 HTTP 进行接口。
Perhaps some developer accidentally enabled it, committed the code, and forgot about it.
也许某个开发人员不小心启用了它,提交了代码,然后忘记了它。
We’ll leave the root-cause-analysis to Citrix themselves.
我们将根本原因分析留给 Citrix 自己。
Now, we have all the parts needed to build an exploit.
现在,我们已经拥有构建漏洞利用所需的所有部分。
We know there is a MSMQ instance with misconfigured permissions, and we know that it uses the infamous BinaryFormatter class to perform deserialization.
我们知道有一个权限配置错误的 MSMQ 实例,并且我们知道它使用臭名昭著的 BinaryFormatter 类来执行反序列化。
The ‘cherry on top’ is that it can be reached not only locally, through the MSMQ TCP port, but also from any other host, via HTTP.
“最重要的是”它不仅可以通过 MSMQ TCP 端口在本地访问,还可以通过 HTTP 从任何其他主机访问。
This combo allows for a good old unauthenticated RCE. Since we’re dealing with a deserialization issue, a bug class that is known for being relatively stable, we can expect a high degree of confidence that our exploit (once crafted) will work reliably – there’s no tricky heap manipulation or other entropy creeping in.
此组合允许使用未经身份验证的旧 RCE。由于我们正在处理反序列化问题,一个以相对稳定而闻名的 bug 类,我们可以期待高度自信地认为我们的漏洞(一旦被制作)将可靠地工作——没有棘手的堆操作或其他熵悄悄进入。
Bob the Packet builder Bob the Packet 构建器
In order to build the exploit packet, we had to dive into the packet’s structure in detail, which turned out to be quite challenging due to limited documentation.
为了构建漏洞利用数据包,我们必须深入研究数据包的结构,由于文档有限,事实证明这非常具有挑战性。
Through plenty of trial and error, we tested different configurations and tweaked various fields by setting up a local test environment that would process MSMQ messages and also enabled HTTP support, the small application we wrote allowed us to experiment with different data types and values until we gradually started to understand how each piece fit together.
通过大量的试验和错误,我们测试了不同的配置,并通过设置一个将处理 MSMQ 消息的本地测试环境来调整各种字段,并且还启用了 HTTP 支持,我们编写的小型应用程序允许我们尝试不同的数据类型和值,直到我们逐渐开始了解每个部分是如何组合在一起的。
Explanation of the Packet Structure
数据包结构说明
Here’s a breakdown of the main parts of this MSMQ HTTP message:
以下是此 MSMQ HTTP 消息的主要部分的细分:
- HTTP Request Line: This is the starting line, indicating that it’s a
POST
request directed at a specific queue endpoint on the server (/msmq/queue_name
). This tells the server we’re sending a new message to the queue.
HTTP 请求行:这是起始行,指示它是针对服务器上的特定队列终结点 (/msmq/queue_name
) 的POST
请求。这告诉服务器我们正在向队列发送一条新消息。 - HTTP Headers: Standard HTTP headers provide basic information about the message:
HTTP 标头:标准 HTTP 标头提供有关消息的基本信息:- Host specifies the server’s address.
Host 指定服务器的地址。 - Content-Type is
multipart/related
, which means the message has multiple parts, like the SOAP envelope and the serialized data payload. It also includes aboundary
value to separate these parts and specifies that the main type in this multipart message istext/xml
.
Content-Type 是多部分/相关的
,这意味着消息具有多个部分,如 SOAP 信封和序列化数据有效负载。它还包括一个边界
值来分隔这些部分,并指定此多部分消息中的主类型为text/xml
。 - SOAPAction defines the action type as
"MSMQMessage"
, informing the server of the kind of message it’s handling.
SOAPAction 将操作类型定义为“MSMQMessage”
,通知服务器它正在处理的消息类型。
- Host specifies the server’s address.
- First Boundary (SOAP Envelope): After the initial headers, we hit the first boundary, which begins the actual message content. The SOAP envelope is an XML structure with two key sections:
First Boundary (SOAP Envelope)(第一个边界(SOAP Envelope)):在初始标头之后,我们到达第一个边界,该边界开始实际的消息内容。SOAP 信封是一个 XML 结构,具有两个关键部分:- Header: Contains routing information (
To
,Action
,MessageID
) that tells the server where this message is going and assigns it a unique ID.
标头:包含路由信息 (To
、Action
、MessageID
),该信息告诉服务器此消息的目的地并为其分配一个唯一 ID。 - Properties: Metadata fields like
ExpiresAt
andSentAt
, which help manage the message’s lifecycle and timing.
属性:元数据字段,如ExpiresAt
和SentAt
,有助于管理消息的生命周期和时间。 - Body: Inside the body, specific fields define the message’s details, such as
Priority
(importance level) andBodyType
(specifying whether the data is binary or text).
Body:在正文中,特定字段定义消息的详细信息,例如Priority
(重要性级别)和BodyType
(指定数据是二进制还是文本)。
- Header: Contains routing information (
- Second Boundary (Serialized Data Payload): The next part, separated by another boundary, holds the actual content of the message. It has:
Second Boundary (Serialized Data Payload)(第二边界 (序列化数据负载)):下一部分由另一个边界分隔,用于保存消息的实际内容。它有:- Content-Type as
application/octet-stream
, which indicates binary data.
Content-Type 设置为application/octet-stream
,表示二进制数据。 - Content-Id links this data back to the SOAP envelope.
Content-Id 将此数据链接回 SOAP 信封。 - Serialized Data: This is the message’s main data payload that gets pushed to the broker queue, in our case a serialized .NET Object
序列化数据:这是消息的主要数据负载,它被推送到代理队列,在我们的例子中是序列化的 .NET 对象
- Content-Type as
These elements work together to create a structured message that MSMQ can route, prioritize, and deliver across networks. By understanding this layout, we could assemble a complete packet that the MSMQ server would accept.
这些元素协同工作,以创建 MSMQ 可以跨网络路由、确定优先级和传递的结构化消息。通过了解此布局,我们可以组装 MSMQ 服务器可以接受的完整数据包。
+--------------------------------------------------+
| HTTP Request Line |
| POST /msmq/queue_name HTTP/1.1 |
+--------------------------------------------------+
| Host: example.com |
| Content-Type: multipart/related; |
| boundary="MSMQ_SOAP_boundary_12345"; |
| type="text/xml" |
| Content-Length: 2100 |
| SOAPAction: "MSMQMessage" |
| Proxy-Accept: NonInteractiveClient |
+--------------------------------------------------+
--MSMQ_SOAP_boundary_12345
+----------------------------------------------------+
| Content-Type: text/xml; charset=UTF-8 |
| Content-Length: 800 |
+----------------------------------------------------+
| SOAP Envelope |
| <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://... |
| <SOAP-ENV:Header> |
| <m:Routing xmlns:m="<http://schemas>..."> |
| <Action>SendMessage</Action> |
| <To><http://example.com/msmq/queue_name></To>|
| <MessageID>uuid:123456789</MessageID> |
| </m:Routing> |
| <m:Properties> |
| <ExpiresAt>20250101T123000</ExpiresAt> |
| <SentAt>20241107T100000</SentAt> |
| </m:Properties> |
| </SOAP-ENV:Header> |
| <SOAP-ENV:Body> |
| <m:MessageBody> |
| <Priority>5</Priority> |
| <BodyType>Binary</BodyType> |
| </m:MessageBody> |
| </SOAP-ENV:Body> |
| </SOAP-ENV:Envelope> |
--MSMQ_SOAP_boundary_12345
--MSMQ_SOAP_boundary_12345
+--------------------------------------------------+
| Content-Type: application/octet-stream |
| Content-Length: 1300 |
| Content-Id: uuid:123456789 |
+--------------------------------------------------+
| MSMQ Message |
| [Binary data or serialized object] |
+--------------------------------------------------+
--MSMQ_SOAP_boundary_12345--
Conclusion 结论
So, what’ve we seen today?
那么,我们今天看到了什么呢?
Well, once again, we’ve lost a little more faith in the Internet.
好吧,我们又一次对 Internet 失去了更多的信心。
We’ve seen how a carelessly-exposed MSMQ instance can be exploited, via HTTP, to enable unauthenticated RCE against Citrix Virtual Apps and Desktops. We’ve walked the reader through crafting an exploit, using an off-the-shelf gadget, resulting in a stable and reliable exploit ‘chain’ which makes for easy exploitation.
我们已经看到如何通过 HTTP 利用粗心暴露的 MSMQ 实例,对 Citrix Virtual Apps and Desktops 启用未经身份验证的 RCE。我们已经引导读者使用现成的小工具制作漏洞利用程序,从而产生一个稳定可靠的漏洞利用“链”,从而轻松利用。
This isn’t really a bug in the BinaryFormatter itself, nor a bug in MSMQ, but rather the unfortunate consequence of Citrix relying on the documented-to-be-insecure BinaryFormatter to maintain a security boundary. It’s a ‘bug’ that manifested during the design phase, when Citrix decided which serialization library to use.
这并不是 BinaryFormatter 本身的 bug,也不是 MSMQ 中的 bug,而是 Citrix 依赖记录在不安全的 BinaryFormatter 来维护安全边界的不幸后果。这是一个“错误”,在设计阶段表现为“错误”,当时 Citrix 决定使用哪个序列化库。
What’s interesting about this particular case is that exploitation was possible even though the MSMQ port was not accessible. The reality is that this is a rabbit hole, and that most (we hope ‘all’) attackers would’ve given up at this point, or even ignored the attack surface entirely.
这个特定案例的有趣之处在于,即使无法访问 MSMQ 端口,也可能被利用。现实情况是,这是一个兔子洞,大多数(我们希望“所有”)攻击者此时都会放弃,甚至完全忽略攻击面。
- Editors note: In fairness, most attackers are probably too busy just sending shell commands to vendor specific management interfaces between firewalls and management appliances. This is a wild guess. Anyway, let’s jump on.
编者注:公平地说,大多数攻击者可能忙于向防火墙和管理设备之间的供应商特定管理接口发送 shell 命令。这是一个疯狂的猜测。无论如何,让我们继续吧。
We reported the deserialization issue to Citrix, along with the condition of HTTP-exposed MSMQ queue, as one issue. It is, at this point, a matter of opinion if this constitutes two individual bugs or one ‘real’ bug. While it is inarguable that Citrix’s use of a BinaryFormatter with untrusted data is a de-facto bug, we don’t have enough context to determine if exposing the MSMQ queue via HTTP is a really a bug, caused by a careless oversight, or a carefully-calculated effect of some obscure business requirement.
我们向 Citrix 报告了反序列化问题,以及 HTTP 公开的 MSMQ 队列的状况,作为一个问题。在这一点上,这是否构成两个单独的 bug 或一个“真正的”错误是一个见仁见智的问题。虽然 Citrix 将 BinaryFormatter 用于不受信任的数据是事实上的错误,但我们无法确定通过 HTTP 公开 MSMQ 队列是否真的是一个错误,是由粗心的疏忽引起的,还是由一些晦涩的业务需求的精心计算的影响引起的。
Either way, after some initial back-and-forth, Citrix managed to reproduce the issue, and after some coordination, a disclosure date of November 12th was mutually agreed.
无论哪种方式,经过一些初步的来回讨论,Citrix 设法重现了该问题,经过一番协调,双方商定了 11 月 12 日的披露日期。
Citrix were friendly in communication and took our report seriously, but at the time of writing, we are not currently aware of version numbers for patches or CVE identifiers for the aforementioned weaknesses. Not the end of the world, and it is what it is.
Citrix 在沟通上非常友好,并认真对待我们的报告,但在撰写本文时,我们目前不知道上述弱点的补丁版本号或 CVE 标识符。不是世界末日,它就是这样。
As ever, remediation advice is simply ‘update to a patched version’. It is difficult to see how this bug could be mitigated otherwise, since the MSMQ interface is such a core part of the way that the application works, and attempting to restrict access to it would likely result in subtle (or not-so-subtle) breakage of the environment.
与往常一样,补救建议只是“更新到修补版本”。很难看出如何以其他方式缓解此 bug,因为 MSMQ 接口是应用程序工作方式的核心部分,尝试限制对它的访问可能会导致环境的细微(或不那么细微)破坏。
The only real thing we are certain of is that the bug exists in the version we analyzed, Citrix_Virtual_Apps_and_Desktops_7_2402_LTSR
. We will, of course, update this post as we learn more about which versions are fixed and which are not. <TBD>
我们唯一可以确定的是,该错误存在于我们分析的版本中。 Citrix_Virtual_Apps_and_Desktops_7_2402_LTSR
当然,随着我们更多地了解哪些版本是固定的,哪些不是,我们将更新这篇文章。
Date 日期 | Event 事件 |
---|---|
July 14th 2024 七月14th,2024 | Initial disclosure to vendor 向供应商进行初步披露 |
August 9th 2024 八月9th,2024 | Vendor reports they are unable to reproduce, requests video of exploitation 供应商报告他们无法复制,要求提供利用视频 |
August 11th 2024 八月11th,2024 | watchTowr responds with video and proof-of-concept exploit watchTowr 以视频和概念验证漏洞作为回应 |
<TBD> | Citrix releases fix in the form of version <TBD> Citrix 以版本 <TBD> 的形式发布修复 |
November 12th 2024 十一月12th,2024 | Disclosure deadline 披露截止日期 |
At watchTowr, we believe continuous security testing is the future, enabling the rapid identification of holistic high-impact vulnerabilities that affect your organisation.
在 watchTowr,我们相信持续安全测试是未来趋势,能够快速识别影响您组织的整体高影响漏洞。
It’s our job to understand how emerging threats, vulnerabilities, and TTPs affect your organisation.
我们的工作是了解新出现的威胁、漏洞和 TTP 如何影响您的组织。
原文始发于Sina Kheirkhah (@SinSinology):Visionaries Have Democratised Remote Network Access – Citrix Virtual Apps and Desktops (CVE Unknown)
转载请注明:Visionaries Have Democratised Remote Network Access – Citrix Virtual Apps and Desktops (CVE Unknown) | CTF导航