Serializer - XmlSerializer và ObjectDataProvider chain
Tiếp theo ta sẽ đến gia phả Serializer với đại diện được phân tích là XmlSerializer
Quá trình Serialize
Đúng như tên gọi thì XmlSerializer
cho phép ta seri object thành XML, hoặc deser từ XML thành object
Để tiến hành sử dụng, đầu tiên mình sẽ có một object sau đây muốn seri thành xml
Mình có đoạn code sau tại hàm main để tiến hành seri và deser
Một lưu ý khi khởi tạo XmlSerializer là: constructor của nó yêu cầu ta phải khai báo type của object muốn serialize
Khi đã khai báo thành công XmlSerializer, khi chạy code ta sẽ có kết quả
Kết quả khi seri:
Kết quả khi deser:
Một lưu ý nho nhỏ là XmlSerializer có một giới hạn, nó không thể seri những property read-only
, tức là những property private hoặc public property mà không có setter đều sẽ được coi là read-only
nên nó không thể seri và quăng lỗi (nguồn: https://stackoverflow.com/questions/13401192/why-are-properties-without-a-setter-not-serialized)
Demo exploit gadget chain ObjectDataProvider
Lý thuyết serialize đủ rồi, giờ mình sẽ bắt đầu exploit 1 gadget chain phổ biến đối với thằng XmlSerializer này là ObjectDataProvider
Mình sẽ dùng .net ysoserial để gen ra payload như sau
Chain của chúng ta sẽ là: ObjectDataProvider -> XamlReader.Parse() -> ObjectDataProvider -> System.Diagnostics.Process.Start("cmd.exe","/c calc")
Nhìn có vẻ đơn giản nhưng sự thật thì không, cụ thể mình sẽ phân tích ở phần sau, còn bây giờ mình sẽ dùng đoạn code sau để demo
Kết quả:
Đoạn code demo trên được tham khảo từ https://github.com/pwntester/ysoserial.net/issues/24 và đó cũng là tái hiện lại sink của CVE-2017-9822 DotNetNuke
Lưu ý khi exploit: có thể thấy nếu muốn exploit thành công gadget này thì ta cần phải kiểm soát được object type sẽ đi vào phần khai báo của XmlSerializer
Phân tích gadget ObjectDataProvider
ObjectDataProvider là gì?
Trước khi đi vào sâu phân tích, ta cần phải biết ObjectDataProvider là gì và nó đóng vai trò gì trong chain ?
Theo docs Microsoft thì ta có định nghĩa như sau:
Đọc thấy chung chung quá, cách hiệu quả nhất để hiểu là dùng thử nó. Mình có đoạn code sau
Note: muốn dùng ObjectDataProvider ta cần namespace System.Windows.Data
, mà namespace này sẽ nằm trong package PresentationFramework
, do đó ta cần phải install package này trước khi thực thi code
Khi đoạn code trên thực thi, calc sẽ popup. Hay nói cách khác khi ObjectDataProvider instance được tạo thì nó sẽ tạo luôn instacne của Process và đồng thời cũng thực thi method Start với argument mà ta truyền vào.
Cụ thể quá trình ObjectDataProvider có thể tham khảo ở đây: https://book.hacktricks.xyz/pentesting-web/deserialization/basic-.net-deserialization-objectdataprovider-gadgets-expandedwrapper-and-json.net#:~:text=while being deserialized.-,How is this possible,-The System.Windows
Vậy thì hành vi này giúp ích gì trong quá trình exploit deser. Ta có thể thấy nếu payload chỉ cần tạo được instace của ObjectDataProvider thì ObjectDataProvider sẽ giúp ta gọi đến Process.Start(”cmd.exe”, “/c calc”)
, tức là ta không cần tìm cách gọi đến method Start để RCE mà chỉ cần gọi được ObjectDataProvider, thì RCE sẽ tự động được trigger
Các bạn có thể thấy được sự tương đồng của ObjectDataProvider với InvokerTransformer
trong chain CC của Java. Nhưng đối với .net thì đây là key quan trọng trong chain
Sau khi hiểu được công dụng của ObjectDataProvider, ta thử áp dụng nó vào quá trình seri/deser của XmlSerializer với đoạn code sau
Đoạn code trên khi thực thi sẽ quăng ra lỗi:
Theo mình tìm hiểu thì có thể lỗi này là do ObjectDataProvider mang trong mình thêm một object type khác là Process, mà giới hạn của XmlSerializer thì chỉ seri/deser được một type object do đó dẫn đến bị lỗi
Để khắc phục tình trạng này thì ta có thể dùng ExpandedWrapper để wrap exploit object lại
ExpandedWrapper
ExpandedWrapper là một class được sử dụng trong hệ thống internal của .net, mục đích ban đầu của nó không phải dành cho dev nên docs ghi khá chung chung và dễ confuse
Theo như mình tìm hiểu thì ExpandedWrapper cho phép "expand" thêm object type vào một object type khác, nghĩa là ExpandedWrapper sẽ tạo ra 1 wrapper mang bên trong 2 hoặc nhiều object type. Đây chính xác là thứ ta cần để bypass hành vi seri hạn chế của XmlSerializer
Ta sẽ có đoạn code sử dụng ExpandedWrapper đơn giản như sau:
Ở đoạn code trên, khi khai báo ExpandedWrapper ta sẽ truyền vào tham số đầu tiên là Process và tham số thứ 2 là ObjectDataProvider. Nghĩa là object type được expand sẽ là ObjectDataProvider và object type sẽ expand vào ObjectDataProvider là Process. Nếu ta muốn thay đổi object type được gọi bởi ObjectDataProvider thì ta sẽ thay thế vào tham số đầu tiên.
Mình sẽ thử serialize object ExpandedWrapper với XmlSerializer với đoạn code sau
Lúc này khi chạy code ta sẽ bị dính lỗi như sau:
Nguyên nhân của lỗi này là do object Process không thể serialize thành XML do nó có kế thừa System.ComponentModel.Component
mà class này có filed site
là một interface. Interface thì không thể serialize thành XML được nên quăng ra lỗi (nguồn https://www.youtube.com/watch?v=os22pgVzXQw phút 24:34)
Do đó cho dù ta đã tìm được cách bypass để seri được nhiều object type thì cũng không thể dùng Process để RCE được, nên ta phải tìm cách khác.
Khi nhìn vào payload của ysoserial ta sẽ thấy cách xử lý của ysoserial là cho ObjectDataProvider trong ExpandedWrapper gọi đến XAMLReader.Parse
, rồi từ XAMLReader.Parse
load một XML độc hại, mà XML độc hại đó khi được load sẽ tạo instance của ObjectDataProvider
và trigger RCE. Đây là một cách rất hay để bypass, cùng mình tiếp tục phân tích
Dùng XAMLReader.Parse để trigger RCE
Đây là phần khiến mình mất rất nhiều thời gian để research và hiểu về nó, sau khi đọc rất nhiều blog và docs thì mình đúc kết được những gì mình hiểu sau đây.
Nói một chút về XAML, thì đây là loại markup language được tạo ra để sử dụng trong WPF (hiểu nôm na là framework code app bằng c# của .net) do đó nếu muốn thực thi được XAML ta phải có một project WPF chuẩn chỉ, hoặc đơn giản hơn ta có thể dùng XAMLReader.Parse
để xử lý XAML.
Quay trở lại với chain, khi đọc thêm docs về ObjectDataProvider ta sẽ thấy dòng sau
Đại khái có nghĩa là trong XAML ta có thể sử dụng ObjectDataProvider để bind object, cho phép ta tạo instance và thực thi method của object như cách ObjectDataProvider được dùng trong code thông thường. Điều này có nghĩa là ta không cần phải seri ObjectDataProvider để trigger RCE nữa, mà chỉ cần đưa trực tiếp chuỗi XAML vào XAMLReader.Parse
, thì ObjectDataProvider instace sẽ được tạo -> trigger RCE. Nhưng làm sao để sử dụng được ObjectDataProvider trong XAML ?
Để sử dụng ObjectDataProvider trong XAML ta sẽ khai báo nó như một element và thường nằm ở phần khai báo resources của XAML
Khái niệm resources trong xaml có nghĩa là những tài nguyên được khai báo và sử dụng nhiều lần (tương tự như biến) và ta có thể khai báo object trong resources bằng nhiều cách (dùng ObjectDataProvider là một trong số cách)
Note: mình không tìm được docs ghi rõ là liệu ObjectDataProvider chỉ dùng được trong phần khai báo resources hay không, nhưng từ khái niệm và cách sử dụng ta cũng có thể ngầm hiểu như vậy (?)
Ví dụ với XAML như sau:
Ở đây ta để ý phần khai báo attribute của element windows:
xmlns và xmlns:x : mặc định có nên ta không cần quan tâm
xmlns:s="clr-namespace:System;assembly=mscorlib"
: define prefix s trỏ đến “clr-namespace:System;assembly=mscorlib” tương tự như package System trong code C# thông thường, nghĩa là ở dưới khi ta dùng s:String là tương tự như System.Stringxmlns:d="clr-namespace:System.Diagnostics;assembly=System"
: define prefix d trỏ đến “clr-namespace:System.Diagnostics;assembly=System” tương tự như package System.Diagnostics, khi đó gọi d:Process sẽ tương tự như System.Diagnostics.Process
Khi này nếu ta thực thi XAML trên bằng XAMLReader.Parse
thì calc sẽ popup.
Tuy nhiên còn một vấn đề nữa là đoạn XAML trên quá dài và bị phụ thuộc vào project, không hề portable. Để khiến payload có thể exploit được với mọi hệ thống ta phải tìm cách khai báo XAML làm sao cho chạy được trên mọi hệ thống thông qua XAMLReader.Parse
ResourceDictionary come to rescue
Khi đào sâu thêm vào các tính năng của XAML cung cấp thì ta biết được có thể khai báo resources vào riêng một file và sử dụng được khắp nơi trong project tính năng này gọi là Resource Dictionaries
, từ đó mang đến khả năng flexible cho XAML → ta có thể lợi dụng
Thay vì dùng Window.Resources
thì ta sẽ dùng ResourceDictionary
để khai báo ObjectDataProvider như sau:
Khi này resource đã hoàn toàn độc lập và có thể dùng được ở mọi nơi
Mình có đoạn code sau để demo:
Kết quả khi thực thi
Cách trên là ta sẽ khai báo ObjectDataProvider trong phần resources để được tự động tạo, hoặc ta cũng có một cách khai báo khác là tự động tạo instance của ObjectDataProvider bằng property ObjectInstance, nghĩa là thay vì khởi tạo gián tiếp qua ResourceDictionary, thì ta sẽ khởi tạo trực tiêp trong ObjectDataProvider (cách này khi nghiên cứu các chain khác thì mình thấy)
Hiệu quả của cả 2 cách là như nhau
Gen payload
Tổng hợp tất cả các ý trên, mình có một đoạn code sau để generate xml payload và deser xml payload
Khi thực thi ta sẽ có được xml payload như sau
Payload này là gần tương tự như payload được gen bởi ysoserial, và khi xml string được deser calc sẽ pop up.
Tóm tắt
Như vậy ta đã hiểu đầy đủ những tính năng và cơ chế cấu thành nên chain ObjectDataProvider
sử dụng trong XmlSerializer. XmlSerializer khi deser sẽ gọi đến XamlReader.Parse, hàm này sẽ Parse malicious XAML để tạo instance của ObjectDataProvider, khi ObjectDataProvider khởi tạo nó sẽ tự động invoke Process.Start → RCE
Điều kiện để chain này hoạt động là:
Target có dùng package
PresentationFramework
Ta có thể kiểm soát được object type khi khởi tạo XmlSerializer constructor
Malicous xml sẽ rơi vào XmlSerializer.Deserialize()
Tuy điều kiện exploit chain này khá ngặt nghèo nhưng nó lại cung cấp những khái niệm cơ bản để ta có thể tìm hiểu các chain sau này
Refer
Last updated