dotnet反序列化之并不安全的SerializationBinder
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.
前几天看到了这篇文章 ,记录一下。
使用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;
}
}
}
解释下代码,有一个RCE的类,通过反序列化cmd字段,然后触发他的tostring方法就可以rce执行命令。
在main函数中,我们先new了一个没有用binder的BinaryFormatter来序列化执行calc命令的RCE对象,在反序列化的时候,绑定了Binder实例做反序列化的类型判断。
在Binder中
通过Type.GetType拿到类型和typeof(RCE)进行比较,如果反序列化类型等于RCE,那么直接返回null,否则返回正确的type。
此时运行一下
发现Binder并没有起作用,calc命令仍然赋值给了RCE的cmd字段。why?
不起作用的Binder
dnspy调试断在binder的return上然后下一步发现
在调用完m_binder.BindToType(assemblyString, typeString)
之后,如果type为空,dotnet会帮我们再次处理类型,也就是FastBindToType()
FastBindToType先从typecache中获取程序集,如果拿不到程序集就尝试进行加载程序集获取type。
其中bSimpleAssembly值取自FEassemblyFormat
而FEassemblyFormat是InternalFE的一个字段
通过 binaryFormatter.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple
我们赋值bSimpleAssembly,如果不赋值默认值也为FormatterAssemblyStyle.Simple
,所以bSimpleAssembly默认为true,接着看
通过 ObjectReader.ResolveSimpleAssemblyName 解析程序集,然后ObjectReader.GetSimplyNamedTypeFromAssembly(assembly, typeName, ref type)
从程序集中拿type
在断点的地方已经拿到了RCE类的type
最终反序列化仍然拿到了RCE的type
而并没有受限于binder的类型绑定。
如何正确使用binder?
其实上文的demo中我已经给了修复的方法,当加载不允许的程序集type时应该直接抛出异常,而不是返回null。
在BlueHat中也提到过 https://www.slideshare.net/MSbluehat/dangerous-contents-securing-net-deserialization
CVE-2022-23277 of exchange
本地没有环境,直接用原作者的图了
exchange的binaryformatter都用到了ChainedSerializationBinder,上图是其实现。
在InternalBindToType返回空值时,不进行ValidateTypeToDeserialize导致黑名单完全不起作用。
InternalBindToType转发到LoadType函数
通过重写GetObjectData让序列化时自定义AssemblyName和FullTypeName
这样在LoadType的Type.GetType(string.Format("{0}, {1}",typeName,assemblyName))
就会抛出异常
抛了异常但是被catch捕获之后相当于LoadType返回了null,那么接着ValidateTypeToDeserialize失效,从而交由FastBindToType获取type,绕过了binder。
当binder返回null值时,binder对反序列化的类型校验不起作用。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK