Note nhanh về BinaryFormatter binder và CVE-2022–23277

Jang
5 min readJun 30, 2022

Vài ngày trước trên blog của codewhitesec có bài nói về rủi ro trong việc sử dụng Binder để phòng chống BinaryFormatter Deserialization, link: https://codewhitesec.blogspot.com/2022/06/bypassing-dotnet-serialization-binders.html.

Như trong blog của codewhitesec cũng đã nói về độ thiếu tin cậy của Binder rồi,

Tuy nhiên đối với mình thì cái này khá là mới và hay ho (điều mà đó giờ mình luôn nghĩ là không tưởng), nên có tìm hiểu và note lại vài dòng ở đây để tiện cho việc sau này đọc lại (do đó bài này có thể sẽ ngắn hơn thường lệ và ko hẳn là 1 blog post)!

###########################

#ROOT CAUSE

Bắt đầu từ việc trong các bản vá lỗi Deserialization của Sharepoint/Exchange đều sử dụng tới Serialization Binder để hạn chế các kiểu dữ liệu sẽ được deserialize:

Các CustomBinder này sẽ override method BindToType()BindToName() để xử lý type name được truyền vào, BindToType() nhận vào 2 tham số là assemblyName và typeName:

Trong quá trình Deserialize, CustomBinder sẽ được gọi từ ObjectReader.Bind(), trong trường hợp các CustomBinder không xử lý được type và trả về null, ObjectReader.Bind() vẫn còn 1 nhánh khác để phân giải kiểu dữ liệu:

Call Stack tới CustomBinder như sau:

Do vậy mà idea của trick này là làm cách nào đó để cái CustomBinder — cái được sinh ra để phòng chống Insecure Deserialization này trả về null, từ đó ObjectReader.Bind() sẽ switch sang nhánh thứ 2 để tự phân giải kiểu dữ liệu.

Tại method FastBindToType(), nếu this.bSimpleAssembly = true (default config), chương trình sẽ gọi tới ResolveSimpleAssemblyName() để phân giải ra Assembly từ assemblyName, nếu giá trị này khác null sẽ đi tiếp vào nhánh GetSimplyNamedTypeFromAssembly()

Tại đây assembly vẫn là mscorlib:

Tiếp tục đi vào method GetSimplyNamedTypeFromAssembly(), FormatterServices.GetTypeFromAssembly() được sử dụng để lấy Type từ Assembly. Trong trường hợp Assembly không tồn tại Type này, chương trình sẽ throw ra các exception (tuy nhiên đều đã được catch), và giá trị của type lúc này vẫn là null. Khi đó, chương trình tiếp tục gọi tới Type.GetType() mà không truyền vào Assembly nữa, chỉ có typeName là được truyền vào để tiếp tục phân giải:

Tại Type.GetType(), nếu không truyền vào assemblyName thì Assembly sẽ được parse từ chính typeName

Và ngay sau đó, Type sẽ được phân giải thành công cùng với Assembly vừa lấy từ typeName mà không cần phụ thuộc vào assemblyName như các bước đầu nữa:

Kết quả là khi kết thúc ObjectReader.Bind(), ta vẫn có được kiểu dữ liệu cần deserialize mà vẫn bypass được sự ngăn chặn của CustomBinder:

#BUILD PAYLOAD

Tóm tắt lại phần root cause thì ta cần phải fake được assemblyName và typeName trong quá trình Serialization!

Và thật may mắn, trong bài viết gốc của tác giả cũng đã đề cập luôn phải làm như thế nào!

GadgetChain được sử dụng ở đây là DataSet.

Idea build payload như sau: Trong .NET BinaryFormatter serialization, một Class có thể override method GetObjectData() để trả về object được serialize (giống như writeObject() của Java). Tại đây ta có thể tùy ý thay đổi các thông tin của object sắp được serialize, bao gồm cả AssemblyName, TypeName, casc Attribute …

Dựa vào đó mình sẽ thay AssemblyName = mscorlib, và giữ nguyên QualifiedTypeName để gen payload

Khi deserialize payload này ta sẽ có kết quả như sau:

#ROOT CAUSE 2

Thực ra để bug này có thể hoạt động được cũng cần thêm một vài sự ràng buộc nhất định với CustomBinder nữa.

Ở đây mình lấy ChainedSerializationBinder của Exchange làm ví dụ, ChainedSerializationBinder.BindToType() gọi tới InternalBindToType() rồi tới LoadType(), method này làm nhiệm vụ xử lý Type.

Trong quá trình debug mình nhận ra là Type.GetType() chắc chắn sẽ đẩy ra FileLoadException nếu không thể load được Type với Assembly vừa truyền vào:

Nếu Exception này không được handle thì chương trình sẽ dừng tại đó luôn, nhánh ObjectReader.Bind() sẽ không thể hoàn thành nữa.

Do vậy điều kiện đủ để có lỗi tại CustomBinder đó là Type.GetType() không được throw Exception!

Thật may mắn ChainedSerializationBinder try catch đầy đủ và không throw ra Exception khi resolve Type lỗi!

Sau khi tìm các vị trí có sử dụng tới ChainedSerializationBinder trên Exchange thì mình nhận ra là nó có chung entrypoint như bug được sử dụng tại Tianfucup (CVE-2021–42321 đã được phân tích tại https://testbnull.medium.com/some-notes-of-microsoft-exchange-deserialization-rce-cve-2021-42321-f6750243cdcd)

Việc khai thác lúc này rất đơn giản, chỉ việc thay gadgetchain vào poc của tianfu là xong ¯\_(ツ)_/¯

PoC video:

--

--