Cũng đã lâu mình chưa lên bài nào về kỹ thuật, lần gần nhất cũng là về entry SharePoint tại kỳ p2o Vancouver 2023.
我已经很久没有写任何技术文章了,上一次也是关于 2023 年 p2o 温哥华的 SharePoint 条目。
Vừa rồi tại p2o Vancouver 2024 mình cũng đã có một entry sharepoint, tuy nhiên kết quả thì mọi người đã biết, mình ko thành công sau 3 lần thử.
最近在 p2o Vancouver 2024 上,我也有一个 sharepoint 条目,但结果大家都知道,我尝试了 3 次都没有成功。
Sau vài ngày setup lại và debug thì mình đã tìm ra nguyên nhân bị fail, bắt nguồn cũng chính từ sự chủ quan và ngộ nhận của mình. Bài viết này một phần là để chia sẻ về những ngộ nhận mà mình và có thể nhiều system admin/re-searcher gặp phải, cũng như phân tích chi tiết về bug (not so 0day) mà mình đã đem vào p2o vừa rồi!
经过几天的复位调试,我找到了失败的原因,源于我的主观性和误解。本文部分是为了分享我和许多系统管理员/重新搜索者遇到的误解,以及对我最近带入 p2o 的错误(不是那么 0day)的详细分析!
Không dài dòng nữa, bắt đầu thôi!
不再啰嗦,让我们开始吧!
#The bug
Mình tình cờ thấy bug này trong quá trình phân tích bản vá tháng 09/2023 (https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-38177).
我在分析 2023 年 9 月补丁时偶然发现了这个错误 ( https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-38177)。
Chi tiết của CVE này mình sẽ ko nêu ở đây (vì thực sự mình chưa reproduce được =]] ), tuy nhiên có một thay đổi đáng chú ý tại SPDistributedCacheUtils.ByteArrayToObject()
:
这个CVE的细节我不会在这里提到(因为我真的没有复制=]]),但是在 SPDistributedCacheUtils.ByteArrayToObject()
以下位置有一个明显的变化:
DataContractSerializer
với type variable đã được sử dụng thay vì NetDataContractSerializer
,
DataContractSerializer
其中 type 变量代替 NetDataContractSerializer
,
Theo mình dự đoán, đây có thể là phần sink của CVE-2023–38177, bởi NetDataContractSerializer
cho deserialize data tùy ý, có thể bị lợi dụng để RCE dễ dàng, còn DataContractSerializer
lại yêu cầu một kiểu dữ liệu rõ ràng trước khi deserialize.
正如我所预测的那样,这可能是 CVE-2023–38177 的接收器部分,因为 NetDataContractSerializer
对于可以轻松用于 RCE 的任意反序列化数据,其余 DataContractSerializer
部分在反序列化之前需要显式数据类型。
Tại đây mình cũng nhận ra đó giờ mình chỉ toàn tập trung vào BinaryFormatter
, XmlSerializer
mà bỏ quên mất NetDataContractSerializer
, cũng là một sink tiềm năng không kém.
在这里我也意识到,现在我全神贯注, BinaryFormatter
XmlSerializer
但忘记了 NetDataContractSerializer
,也是一个同样潜在的下沉。
Tiếp tục trace back từ SPDistributedCacheUtils.ByteArrayToObject()
để tới được SPDistributedCache.GetObject()
继续追溯到 SPDistributedCacheUtils.ByteArrayToObject()
到达 SPDistributedCache.GetObject()
Tại đây ta có thể thấy, byte[] array
được lấy ra từ this._dataCachePointer
, sau đó sẽ được truyền vào và deserialize bằng NetDataContractSerializer()
在这里,我们可以看到, byte[] array
取自 this._dataCachePointer
,然后将由 NetDataContractSerializer()
注入和反序列化
Bạn có nghĩ this._dataCachePointer
đơn thuần chỉ là một dạng Cache Map lưu trực tiếp trên memory của process này, this._dataCachePointer.Get()
chỉ là lấy element từ Map này mà thôi? Mình cũng đã từng nghĩ đơn giản như vậy và thường auto skip những đoạn call như vậy khi review code.
你认为 this._dataCachePointer
它只是直接存储在这个进程内存中的一种缓存映射形式, this._dataCachePointer.Get()
只是从这个映射中获取元素吗?我曾经认为很简单,并且在审查代码时经常自动跳过此类调用。
Tuy nhiên sự thật không phải như vậy, nội dung của this._dataCachePointer.Get()
như sau:
但事实并非如此,其 this._dataCachePointer.Get()
内容如下:
DataCache.Get() -> DataCache.InternalGet() -> DataCache.SendReceive()
DataCache.Get() -> DataCache.InternalGet() -> DataCache.SendReceive()
Tại method DataCache.SendReceive()
, nếu reqMsg.UserObject != null
, reqMsg.UserObject
sẽ được serialize bằng Utility.SerializeUserObject()
sau đó được gửi đi bằng RoutingClient.SendMsgAndWait()
[1].
在方法 DataCache.SendReceive()
中,如果 reqMsg.UserObject != null
, reqMsg.UserObject
将由 Utility.SerializeUserObject()
RoutingClient.SendMsgAndWait()
[1] 发送。
this._clientDRM.ProcessRequest()
sẽ gọi tiếp tới DRM.SendToDestination()
-> RequestBody.Send()
-> DRM.SendRequest()
-> WcfClientChannel.Send()
Tại đây, mình biết được this._dataCachePointer.Get()
không đơn thuần chỉ là một cái Map để lưu cache, mà nó sẽ phải thực hiện gửi một request tới cache server thông qua WCF Binding để lấy dữ liệu về, cache server này trên sharepoint được gọi là AppFabric Server/DistributedCache Service.
在这里,我知道 this._dataCachePointer.Get()
它不仅仅是一个存储缓存的映射,而是必须通过 WCF 绑定向缓存服务器发出请求才能获取数据,SharePoint 上的这个服务器缓存称为 AppFabric Server / DistributedCache Service。
*Lesson learnt: không bao giờ bỏ qua một chi tiết nào dù trông nó có nhỏ bé vô hại.
*经验教训:永远不要遗漏一个细节,即使它看起来无害。
Quay trở lại phần [1], ta có thể thấy trước khi gửi Msg, dữ liệu được serialize bằng Utility.SerializeUserObject()
, method này gọi tới Utility.NetDataContractSerialize()
để serialize data:
回到 Utility.NetDataContractSerialize()
第 [1] 节,我们可以看到,在发送 Msg 之前,数据被 Utility.SerializeUserObject()
序列化了,此方法调用来序列化数据:
Như vậy, chắc chắn ở phía Cache Server sẽ phải có phần xử lý serialized data này.
因此,在缓存服务器端肯定必须进行这种序列化的数据处理。
— — — — —
Chuyển target sang AppFabric Server, service này được chạy tại port 22233, 22234, 22236 và được mở kết nối firewall
将目标切换到 AppFabric Server,此服务在端口 22233、22234、22236 上运行,并打开防火墙连接
Đây là một Managed Process nên mình hoàn toàn có thể attach dnSpy vào và debug, set breakpoint tại NetDataContractSerializer.ReadObject()
và process đã hit breakpoint vài giây sau đó:
这是一个托管进程,因此我可以完全附加 dnSpy 并调试,将断点 NetDataContractSerializer.ReadObject()
设置为,进程在几秒钟后达到断点:
Method VelocityDataStore.ProcessMessageRequest()
có nhiệm vụ xử lý các Wcf Request được gửi tới AppFabric Server, tại đây, nếu request có dạng Notification Request, request.Value
sẽ được deserialize bằng Utility.Deserialize()
.
该方法 VelocityDataStore.ProcessMessageRequest()
负责处理发送到 AppFabric Server 的 Wcf 请求,如果请求采用通知请求的形式, request.Value
则将使用 Utility.Deserialize()
.
Method này hỗ trợ nhiều kiểu deserialize dữ liệu, trong đó có NetDataContractSerializer()
:
此方法支持多种类型的数据反序列化,包括 NetDataContractSerializer()
:
Do checkTypeToLoad được set = true, CustomSerializationBinder sẽ được sử dụng để check Type. Tuy nhiên cũng không cần lo lắng lắm, Binder này chỉ đơn thuần là kiểm tra xem có tìm được Assembly trong domain hiện tại hay không, chứ không restrict Type!
由于 checkTypeToLoad 设置为 = true,因此将使用 CustomSerializationBinder 检查 Type。但是,别担心,此 Binder 只是检查是否在当前域中找到程序集,而不是限制类型!
=> Tóm lại ta có thể gửi một Request Msg với type = notification và Value chưa deserialization gadgetchain để RCE?!
=> 简而言之,我们可以在没有反序列化小工具链的情况下向 RCE 发送带有 type=notification 和 Value 的 Msg 请求吗?!
Mình đã thử viết script để send payload, tuy nhiên do chưa có thư viện python nào hỗ trợ tương tác với WCF net.tcp binding, mình dùng tạm thư viện có sẵn của AppFabric Client, gọi tới bằng Reflection. PoC Script được gửi chi tiết ở cuối bài!
我尝试编写脚本来发送有效负载,但由于没有支持与 WCF net.tcp 绑定交互的 python 库,因此我暂时使用 AppFabric 客户端的现有库,使用 Reflection 调用。PoC脚本在帖子末尾详细提交!
PoC in action: https://www.youtube.com/watch?v=6O2X9ehNQ3Y
PoC 的实际应用:https://www.youtube.com/watch?v=6O2X9ehNQ3Y
…. ….
…
# The mistake # 错误
Yeah, câu chuyện sẽ là happy ending nếu chỉ dừng lại ở đó, nhưng thực tế không như vậy ¯\_(ツ)_/¯.
是的,如果故事就此止步,那将是一个幸福的结局,但̄\_(ツ)_/ ̄事实并非如此。
Mãi đến tận lúc vào demo tại p2o, mình vẫn khá tự tin là PoC hoàn chỉnh, không có một exception gì có thể cản được cả, tuy nhiên mình đã fail sau cả 3 lần thử, tất cả 3 lần đều trả về chung 1 lỗi mà mình chưa gặp bao giờ.
直到进入 p2o 的演示,我仍然很有信心 PoC 已经完成,没有例外可以阻止它,但我在 3 次尝试后都失败了,所有 3 次都返回了我以前从未遇到过的相同错误。
Ban đầu mình còn nghĩ có thể do máy ảo của BTC bị lag (đúng là trước đó nó rất lag, mất rất lâu để gửi được request tới). Tuy nhiên vào lần thử cuối cùng, request gửi rất nhanh nhưng lỗi vẫn như vậy.
起初,我以为可能是因为BTC的虚拟机滞后了(确实,在此之前它非常滞后,发送请求需要很长时间)。但是,在最后一次尝试时,请求发送得非常快,但错误仍然相同。
Mình có tìm qua thông tin về lỗi này trên google và được câu trả lời là do máy chủ yêu cầu thêm cơ chế xác thực nhưng phía client cung cấp không đủ. Có nghĩa là mình đã bị miss mất đoạn authentication.
我在谷歌上搜索了有关此错误的信息并得到了答案,因为服务器需要更多的身份验证机制,但提供的客户端还不够。这意味着我错过了身份验证。
Đối với WCF Binding, phương thức authentication sẽ được setup bằng element securityProperties:
对于 WCF 绑定,将使用 securityProperties 元素设置身份验证方法:
<securityProperties mode="Transport" protectionLevel="EncryptAndSign" />
Trên AppFabric Server, config này có thể được export bằng command:
在 AppFabric Server 上,可以使用以下命令导出此配置:
export-cacheclusterconfig -file C:\temp\aa.txt
Với môi trường test của mình, AppFabric Cache đang có config như sau:
在我的测试环境中,AppFabric 缓存的配置如下:
Theo như config này, mode = None và protectionLevel = None sẽ cho phép người dùng access tới Distribute Cache Server mà không cần qua bước xác thực nào cả!
根据此配置,mode=None 和 protectionLevel=None 将允许用户无需任何身份验证即可访问分发缓存服务器!
Điều đó dẫn tới việc mình có thể gửi payload tùy ý tới môi trường test mà không gặp vấn đề gì cả.
这使我能够毫无问题地将任意有效载荷发送到测试环境。
Một điều đáng lưu ý là môi trường SharePoint Server của mình đã được install bản vá mới nhất của Microsoft tại thời điểm đó (March 2024 patch).
值得注意的是,我的 SharePoint Server 环境当时安装了 Microsoft 的最新补丁(2024 年 3 月补丁)。
Vậy điều gì đã thực sự xảy ra?
那么到底发生了什么?
..
Sau vài ngày lần mò trên khắp diễn đàn của Microsoft, mình đã gặp một topic như vậy:
在Microsoft的论坛上摸索了几天后,我遇到了一个这样的帖子:
Tại bản vá February 2023, user trên đã gặp phải một lỗi liên quan tới “SecurityMode và ProtectionLevel”, điều này chắc chắn có liên quan tới phần Wcf Binding của AppFabric Cache.
在 2023 年 2 月的修补程序中,用户遇到了与“SecurityMode 和 ProtectionLevel”相关的错误,这肯定与 AppFabric 缓存的 Wcf 绑定部分有关。
Lội sâu hơn vào phần comment, mình biết được từ bản vá January 2023 đã có thay đổi ở phần SecurityMode và ProtectionLevel của SharePoint. Điều đó có nghĩa là cái bug mà mình tìm ra thực ra đã bị người khác thấy và fix từ lâu rồi.
更深入地进入评论部分,我从 2023 年 1 月的补丁中了解到,SharePoint 的 SecurityMode 和 ProtectionLevel 部分发生了变化。这意味着我发现的错误实际上很久以前就被其他人看到并修复了。
Quay trở lại với cái topic trên, một phần đáng chú ý trong topic trên, đó là tác giả có đề cập tới việc chạy SharePoint Products Configuration Wizard sau khi update server:
回到上面的主题,上面主题的一个值得注意的部分是,作者提到在更新服务器后运行 SharePoint 产品配置向导:
Mình thấy ngờ ngợ có gì đó sai sai ở đây, vì đó giờ mình chỉ cài bản vá xong là xong, chưa bao giờ chạy thêm gì sau đó cả!
我怀疑这里出了点问题,因为现在我刚刚完成了补丁的安装,之后再也没有运行过任何东西!
Sau khi kiểm chứng lại thông tin từ Microsoft (https://learn.microsoft.com/en-us/sharepoint/upgrade-and-update/install-a-software-update) và test trên chính lab của mình, exploit không còn hoạt động nữa. Và config mới của AppFabric Cache đã được thay đổi như sau:
在验证了来自 Microsoft (https://learn.microsoft.com/en-us/sharepoint/upgrade-and-update/install-a-software-update) 的信息并在其自己的实验室中进行测试后,该漏洞不再有效。AppFabric 缓存的新配置已更改如下:
Yeah, that moment he knew he fuked up
是的,那一刻他知道他搞砸了
Mình đã thử hỏi nhiều anh em re-searcher, system admin khác gần như 80% đều nghĩ chỉ cần cài bản vá là xong.
我试图询问许多重新搜索者,其他系统管理员几乎 80% 认为只需安装补丁即可完成。
Như vậy, theo mình thì hiện tại còn khá nhiều máy chủ SharePoint cũng gặp tình trạng chỉ được cài bản vá nhưng chưa chạy Products Configuration Wizard nên các target này vẫn bị ảnh hưởng bởi bug trên!
因此,在我看来,目前有相当多的 SharePoint 服务器仅进行了修补但尚未运行产品配置向导,因此这些目标仍然受到上述 bug 的影响!
Bài học mà mình rút ra ở đây là:
我在这里学到的教训是:
P/s: Mặc dù port 22233 chỉ mở trong mạng nội bộ, tuy nhiên ai đảm bảo được trong mạng nội bộ này không có tổ chức nào đang nằm vùng ( ͡° ͜ʖ ͡°). Nên giới hạn port này chỉ trong phạm vi của các máy chủ sharepoint với nhau thôi!
P/s:虽然22233端口只在内网开放,但谁能保证这个内网没有( ͡° ͜ʖ ͡°)组织。此端口应仅限于 SharePoint 服务器!
PoC project: https://github.com/testanull/SharePoint-not-so-0day/
PoC项目:https://github.com/testanull/SharePoint-not-so-0day/
原文始发于Jang:SharePoint not-so 0day (How I’ve failed at p2o Vancouver 2024)
转载请注明:SharePoint not-so 0day (How I’ve failed at p2o Vancouver 2024) | CTF导航