4

dotnet反序列化之并不安全的SerializationBinder

 2 years ago
source link: https://y4er.com/post/dotnet-deserialize-bypass-binder/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前几天看到了这篇文章 ,记录一下。

使用SerializationBinder

先来一个demo,用SerializationBinder限制一下反序列化的类型。

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

namespace Serialize
{
    internal class Program
    {
        static void Main(string[] args)
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            MemoryStream memoryStream = new MemoryStream();
            RCE calc = new RCE("calc");
            binaryFormatter.Serialize(memoryStream, calc);


            memoryStream.Position = 0;
            binaryFormatter.Binder = new MyBinder();
            object v = binaryFormatter.Deserialize(memoryStream);
            Console.WriteLine(v);
            Console.ReadKey();
        }
    }

    [Serializable]
    class RCE
    {
        public string cmd;

        public RCE(string cmd)
        {
            this.cmd = cmd;
        }

        public override string ToString()
        {
            return $"exec cmd:{cmd}";
        }
    }
    class MyBinder : SerializationBinder
    {
        public override Type BindToType(string assemblyName, string typeName)
        {
            Console.WriteLine($"assemblyName:{assemblyName},typeName:{typeName}.");
            Type typeToDeserialize = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));

            if (typeToDeserialize.Equals(typeof(RCE)))
            {
                //throw new Exception("can't deseriliza rce class.");
                Console.WriteLine("can't deseriliza rce class.");
                return null;
            }
            return typeToDeserialize;
        }
    }
}
csharp

解释下代码,有一个RCE的类,通过反序列化cmd字段,然后触发他的tostring方法就可以rce执行命令。

在main函数中,我们先new了一个没有用binder的BinaryFormatter来序列化执行calc命令的RCE对象,在反序列化的时候,绑定了Binder实例做反序列化的类型判断。

在Binder中

1.png

通过Type.GetType拿到类型和typeof(RCE)进行比较,如果反序列化类型等于RCE,那么直接返回null,否则返回正确的type。

此时运行一下

2.png

发现Binder并没有起作用,calc命令仍然赋值给了RCE的cmd字段。why?

不起作用的Binder

dnspy调试断在binder的return上然后下一步发现

3.png

在调用完m_binder.BindToType(assemblyString, typeString)之后,如果type为空,dotnet会帮我们再次处理类型,也就是FastBindToType()

4.png

FastBindToType先从typecache中获取程序集,如果拿不到程序集就尝试进行加载程序集获取type。

其中bSimpleAssembly值取自FEassemblyFormat

5.png

而FEassemblyFormat是InternalFE的一个字段

6.png

通过 binaryFormatter.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple我们赋值bSimpleAssembly,如果不赋值默认值也为FormatterAssemblyStyle.Simple,所以bSimpleAssembly默认为true,接着看

7.png

通过 ObjectReader.ResolveSimpleAssemblyName 解析程序集,然后ObjectReader.GetSimplyNamedTypeFromAssembly(assembly, typeName, ref type)从程序集中拿type

在断点的地方已经拿到了RCE类的type

8.png

最终反序列化仍然拿到了RCE的type

9.png

而并没有受限于binder的类型绑定。

如何正确使用binder?

其实上文的demo中我已经给了修复的方法,当加载不允许的程序集type时应该直接抛出异常,而不是返回null。

10.png

在BlueHat中也提到过 https://www.slideshare.net/MSbluehat/dangerous-contents-securing-net-deserialization

11.png

CVE-2022-23277 of exchange

本地没有环境,直接用原作者的图了

12.png

exchange的binaryformatter都用到了ChainedSerializationBinder,上图是其实现。

在InternalBindToType返回空值时,不进行ValidateTypeToDeserialize导致黑名单完全不起作用。

InternalBindToType转发到LoadType函数

13.png

通过重写GetObjectData让序列化时自定义AssemblyName和FullTypeName

14.png

这样在LoadType的Type.GetType(string.Format("{0}, {1}",typeName,assemblyName))就会抛出异常

15.png

抛了异常但是被catch捕获之后相当于LoadType返回了null,那么接着ValidateTypeToDeserialize失效,从而交由FastBindToType获取type,绕过了binder。

当binder返回null值时,binder对反序列化的类型校验不起作用。

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK