Microsoft Exchange From Deserialization to Post-Auth RCE (CVE-2021–28482)

Microsoft Exchange From Deserialization to Post-Auth RCE (CVE-2021–28482)

·

7 min read

Sau sự kiện proxylogon xảy ra vào tháng 3 vừa rồi, có vẻ như đã tiếp một nguồn cảm hứng mới cho các Researcher đã/đang làm về Exchange.

Attack vector này khá là mới, không đi theo những lối mòn về lỗ hổng trên Exchange, có lẽ như trước đó chưa ai từng tìm ra chain attack nào trên Exchange như vậy.

Cũng vào thời điểm đó là đang diễn ra Pwn2Own 2021 Vancouver, trong list target có Exchange được treo thưởng với 200k$USD ~= 5 tỏi VND …

Đây có lẽ là target hot nhất trong đợt p2o này, có tới 3 team cùng target vào Exchange, trong đó có: Orange from Devcore, Phạm Khánh đến từ Viettel, Steven from SrcIncite.

Mặc dù ngay trong lần thử đầu tiên, team Orange đã thành công và ăn trọn 200k$, nhưng độ nóng của các ngày sau mỗi khi tới target Exchange là không hề giảm. Mình, đồng nghiệp, và cũng như bao researcher khác cũng chỉ trông ngóng tới khi target Exchange lên sóng, để thỏa mãn tính tò mò cũng như xem author có rơi rớt được thông tin nhỏ nhoi gì trong quá trình PoC hay không 🤣. Dù 2 bug về sau đều bị đánh là Partial, có lẽ là do dup với bug đầu tiên của orange, nhưng chúng tôi vẫn rất nể phục họ, đen đủi chỉ là do sự sắp xếp trước sau mà thôi, chứ độ khủng của 3 chain mà 3 team mang đi attack đều như nhau cả.

Một tuần sau đó, 13/04, M$ release bản vá, cũng hơi bất ngờ một chút khi trong lần release này có tới 2/4 CVE của Exchange là pre-auth, và tất cả 4 CVE đó đều có thể RCE cả. Bất ngờ ở chỗ là p2o vừa diễn ra tuần trước, mà tuần này đã có patch thì có thể đây là 1 bug hoàn toàn khác so với đám bug được sử dụng tại p2o kia.

#DIFF PATCH

Sau vụ proxylogon, Exchange có vẻ tự dưng lại hot lên.

Lần ra patch, rất nhiều bên cùng đổ xô vào xâu xé cái patch, từ xã hội đen cho tới xã hội đỏ, nơi nào cũng tập trung tới 80% nhân lực vào làm vụ này.

Và dĩ nhiên, mình cũng ko là ngoại lệ ( ͡° ͜ʖ ͡°).

Ban đầu mục tiêu của mình với đồng nghiệp là nghiên cứu 2 lỗi pre-auth trước, có lẽ nhiều bên khác cũng như vậy.

Tuy nhiên việc này ko hề đơn giản như bọn mình nghĩ, và đã không thành công trong việc phân tích 2 bug pre-auth này,

Dưới đây chỉ là một số lưu ý về 2 bug pre-auth CVE-2021–28480 và 28481:

Tại BackEndCookieEntryParser.TryParse(), một vài dòng code mới thêm vào để kiểm tra lại host name và FQDN:

Có thể tại đây cũng đã từng xảy ra lỗi giống như X-BEResource-Cookie của proxylogon, có thể lợi dụng để set BackEndServer sau đó SSRF vào backend.

Class này xử lý cookie dạng: X-BackEndCookie/X-BackEndCookie2.

BackEndCookieEntryParser.TryParse() -> UnObscurify(). Method này hoạt động đơn giản là decode base64 cookie và xor với char “0xff”:

Sau khi decode một chuỗi từ X-BackEndCookie có dạng như sau:

**Database~**eb60615b-fc77-44b7-b0e4-a7abf6f7f57e**~~**2021-05-20T01:48:22

Bên cạnh kiểu Database, còn có 1 kiểu khác là Server:

Dạng như sau:

**Server**~**exchange.evil.corp**~1942062522~2021-05-19T08:36:11

Nhìn trông thì rất giống so với lỗi lần trước của X-BEResource,

Tuy nhiên, để lợi dụng được bug của X-BackEndCookie này thì không phải entrypoint nào cũng được!

Cookie này chỉ được nhận và xử lý bởi các class kế thừa class BEServerCookieProxyRequestHandler:

Một số class kế thừa của nó là:

Đến đó chưa phải là hết, câu chuyện vẫn còn tiếp diễn!

Các class trên đều kế thừa ProxyRequestHandler, sau mỗi lần tính giá trị BackEnd thành công, method DoProtocolSpecificRoutingTargetOverride() -> RedirectIfNeeded() đều được gọi:

Trong đó, class OwaEcpProxyRequestHandler lại override method RedirectIfNeeded() này:

Nó xử lý và tìm kiếm sự tồn tại của FQDN trong hệ thống, nếu không có sẽ throw exception luôn.

Đây chính là lý do mà nhiều bên cũng đã phát hiện và thử BackEndCookie này nhưng lại bị fail ở các entrypoint “/ecp, /owa”.

Để xử lý nó thì cần phải tìm được các class thừa kế class BEServerCookieProxyRequestHandler, nhưng method RedirectIfNeeded() cũng phải thỏa mãn không tác động tới giá trị FQDN kia.

Trong đám đó mình có tìm ra được một vài class khả thi như sau:

- AnonymousCalendarProxyRequestHandler
- ComplianceServiceProxyRequestHandler
- EwsAutodiscoverProxyRequestHandler
- MailboxDeliveryProxyRequestHandler
- MapiProxyRequestHandler
- MicroServiceProxyRequestHandler
- MrsProxyRequestHandler
- OabProxyRequestHandler
...

Tuy nhiên để xét cho trường hợp unauthenticated, thì mình mới chỉ vào được AnonymousCalendarProxyRequestHandler EwsAutodiscoverProxyRequestHandler.

Mặc dù đã có thể set giá trị FQDN tùy ý, nhưng trước khi request, ProxyRequestHandler vẫn check lại một lần nữa giá trị Host == BackEndServer.Fqdn

Nếu như cố tình sửa láo giá trị FQDN để SSRF như proxylogon, sẽ bị exception tại đây luôn!

Do đó tất cả những gì chúng ta có thể làm bây giờ là sửa thành một cái FQDN hợp lệ bất kỳ nào đó,

Với trường hợp AnonymousCalendarProxyRequestHandler:

Host có thể thành bất kỳ, nhưng sẽ bị lỗi do server không gen được gói để authenticate với backend -> chỉ có thể sửa thành backend của hệ thống.

Với trường hợp EwsAutodiscoverProxyRequestHandler:

Có thể đẩy request tới một host bất kỳ, tuy nhiên request này lại chứa token của user Anonymous, nói cách khác là không có quyền gì nhiều nếu gửi vào backend.

Việc xem xét bug tại backend cookie của mình bị dừng tại đây hơn một tuần và cho tới tận bây giờ vẫn không có tiến triển gì thêm.

Đó là những gì mình muốn share về 2 bug pre-auth này,

Tiếp theo là về bug Post-Auth RCE — CVE-2021–28482:

Trong bản vá lần này, có 2 file bị xóa khỏi server Exchange đó là:

  • Microsoft.Exchange.Clients.Owa2.Server.Web.MeetingPollHandler

  • Microsoft.Exchange.HttpProxy.PsgwProxyRequestHandler

Với Psgw thì mình có đào bới khắp nơi những vẫn không tìm ra entrypoint để tiếp cận được, do đó mình quay qua xem xét MeetingPollHandler.

Dựa vào web.config của ClientAccess/owa, có thể biết được các tiếp cận entrypoint này, đó là /owa/MeetingPollHandler.ashx:

Đoạn code xử lý của MeetingPollHandler như sau:

MeetingPollHandler.ProcessRequest()

-> MeetingPollProposeOptionsPayload.ProcessRequest()

Và điều đặc biệt hơn nằm ở bên trong method MeetingPollProposeOptionsPayload.GetRequests()

Tại đây, method này gọi tới EntitySerializer.Deserialize(), EntitySerializer.Deserialize() gọi tiếp tới DataContractSerializer.ReadObject()

Mình từng làm nhiều về Java Deser, nhưng với .Net deser thì chưa một lần nào,

Tuy nhiên thông qua tìm kiếm và hỏi thăm từ nhiều bên mình được biết là hoàn toàn có thể RCE với DataContractSerializer, nếu như có thể điều khiển được kiểu dữ liệu để deserialize, hoặc là kiểu dữ liệu để deserialize có lỏng lẻo gì đó có thể lợi dụng!

Ở đây thì nó là trường hợp thứ 2, kiểu dữ liệu được deserialize quá lỏng lẻo, chúng ta cùng xem kỹ hơn.

Dữ liệu được deserialize như sau:

EntitySerializer.**Deserialize**<Dictionary<string, **ProposeOptionsMeetingPollParameters**>>(largeStringProperty);

class ProposeOptionsMeetingPollParameters kế thừa SchematizedObject

SchematizedObject kế thừa PropertyChangeTrackingObject.

Mấu chốt của lỗ hổng này nằm tại chính class PropertyChangeTrackingObject:

Class này có thêm một Nested class nữa để chứa thông tin của các Entity, trong đó các entity này được lưu vào field ChangedProperties, field này có kiểu dữ liệu là PropertyBag.

DataMember của PropertyBag chỉ có 1 field duy nhất với kiểu Dictionary<string, object>:

¯_(ツ)_/¯

Theo đúng logic này, khi deserialize với Type: Dictionary<string, ProposeOptionsMeetingPollParameters> của đoạn xử lý MeetingPollHandler, mình hoàn toàn có thể gài một gadgetchain vào field propertyValues của field ChangedProperties khi deserialize, (việc này cũng khá là giống trong Java Deserialization)!

Logic được diễn giải bằng hình như sau:

GadgetChain mình sử dụng là ObjectDataProvider, được gen từ ysoserial.net.

Tuy nhiên cũng cần nhiều tùy chỉnh để ăn khớp với cách hoạt động của Exchange Server:

Việc tiếp theo phải làm đó là tạo một meeting ròi đẩy gadget vào (tạo như thế nào thì có thể search document)

Cuối cùng là trigger bằng url có dạng:

Với XXXX chính là ID của meeting có chứa payload.

Sau khi trigger bằng MeetingPollHandler, cmd sẽ được spawn trên server Exchange với parent process là w3wp của OWA App:

PoC:

Đây chỉ là một trong số nhiều entrypoint có thể trigger được lỗ hổng này, những entrypoint khác có thể trigger mà không cần tác động tới. Tuy nhiên mình chưa nghiên cứu kỹ và PoC được trong thời gian này.

Hy vọng bài viết này có thể đem lại phần nào đó tháo gỡ các vướng mắc mà nhiều người đang nghiên cứu gặp phải, biết đâu họ sẽ tìm ra được hướng đi hay hơn ;).

Thanks for reading,

Jang of VNPT ISC