浅谈SharePoint漏洞之CVE-2025-53770分析失败篇

admin 2025-12-22 04:22:18 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文分享了SharePoint漏洞CVE-2025-53770/CVE-2025-49704的分析失败经验,作者首先修正了上篇文章中的错误,指出实际分析的是CVE-2025-49706而非CVE-2025-53771。通过错误堆栈信息定位到漏洞相关的WebPart解析方法,并详细描述了复现漏洞时遇到的失败经验,包括PoC可能存在的问题。文章最后提供了SharePoint武器化的一些实用笔记,如版本判断方法、鉴权绕过技巧和持久化方案,强调了漏洞利用手法的重要性。 综合评分: 88 文章分类: 漏洞分析,渗透测试,WEB安全,漏洞POC,应急响应


cover_image

浅谈SharePoint漏洞之CVE-2025-53770分析失败篇

原创

MG

不吃猹的瓜

2025年7月27日 16:46 日本

在本篇的开头先修正上篇文章中一个先入为主的错误,笔者以为自己分析的是CVE-2025-53771,但在写完文章后回头看了补丁才发现分析的是CVE-2025-49706。这俩漏洞本质是一样的,另外据不完全统计,如今公开的PoC都是CVE-2025-49706PoC。以上对普通读者来说可能没有太大的意义,做流量检测的读者们可以关注下。笔者这里就不再赘述了,因为卡巴出了篇比较详尽的分析[1],感兴趣的读者可以自行阅读。在这一篇中,笔者主要是想分享一下分析复现CVE-2025-49704/CVE-2025-53770时的失败经验。

漏洞点定位

继续上篇的请求分析,看到在SharePoint会在返回包的头中加入request-id用于标识请求,既然能定位到请求,那毫不犹豫地继续开启日志大法(偷懒大法),根据request-id在日志中很容易就找到了错误的堆栈信息:

以上错误引起了笔者的注意,笔者推断此方法或许与CVE-2025-49704/CVE-2025-53770有关系,该方法如下所示:

internal&nbsp;static&nbsp;MarkupProperties&nbsp;GetPartPreviewAndPropertiesFromMarkup(Uri pageUri,&nbsp;string&nbsp;webPartMarkup,&nbsp;bool&nbsp;clearConnections, SPWebPartManager manager, SPWeb web, MarkupOption markupOption,&nbsp;bool&nbsp;bConvertWebPartFormatBehavior,&nbsp;bool&nbsp;prependRegisterDirectivesToMarkup,&nbsp;ref&nbsp;WebPart frontPagePart,&nbsp;ref&nbsp;string&nbsp;markupStorageKey,&nbsp;ref&nbsp;string&nbsp;frontPageZoneId,&nbsp;ref&nbsp;WebPartImporter webPartImporter,&nbsp;ref&nbsp;List<RegisterDirectiveData> registerDirectiveDataList,&nbsp;ref&nbsp;IServerDocumentDesigner documentDesigner)
{
&nbsp;MarkupProperties markupProperties =&nbsp;new&nbsp;MarkupProperties();
&nbsp;frontPagePart =&nbsp;null;
&nbsp;markupStorageKey =&nbsp;null;
&nbsp;bool&nbsp;flag =&nbsp;false;
&nbsp;...
}

方法的整体逻辑太长了,暂时先跳过,简单来说此函数的作用就是解析和实例化一个WebPart。回到堆栈错误,发现引发错误的原因是因为使用了空引用,也就是说要想完成实例化必须得传入相关参数进行解析,十分合理!开始遍历CodeBase,发现了如下方法:

internal&nbsp;WebPart SelectedAspWebPart
{
get
&nbsp;{
if&nbsp;(!this._selectedWebPartSet)
&nbsp; {
&nbsp; &nbsp;this._selectedWebPartSet =&nbsp;true;
&nbsp; &nbsp;if&nbsp;(this.InCustomToolPane &&&nbsp;this.SPWebPartManager.DisplayMode == WebPartManager.EditDisplayMode)
&nbsp; &nbsp;{
&nbsp; &nbsp;&nbsp;if&nbsp;(this.frontPageWebPart ==&nbsp;null)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp;try
&nbsp; &nbsp; &nbsp;{
&nbsp; &nbsp; &nbsp;&nbsp;stringvalue&nbsp;= SPRequestParameterUtility.GetValue<string>(this.Page.Request,&nbsp;"MSOTlPn_Uri", SPRequestParameterSource.Form); -->&nbsp;1
&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(value&nbsp;!=&nbsp;null&nbsp;&&&nbsp;value.Length >&nbsp;0)
&nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp;this.frontPageUri =&nbsp;new&nbsp;Uri(value);
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; &nbsp;catch&nbsp;(Exception)
&nbsp; &nbsp; &nbsp;{
&nbsp; &nbsp; &nbsp;&nbsp;this._errorText = WebPartPageResource.GetString("CantLoadWebPartIntranet");
&nbsp; &nbsp; &nbsp;&nbsp;this._selectedWebPart =&nbsp;null;
&nbsp; &nbsp; &nbsp;&nbsp;returnthis._selectedWebPart;
&nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; &nbsp;MarkupProperties partPreviewAndPropertiesFromMarkup = ToolPane.GetPartPreviewAndPropertiesFromMarkup(this.frontPageUri, SPRequestParameterUtility.GetValue<string>(this.Page.Request,&nbsp;"MSOTlPn_DWP", SPRequestParameterSource.Form),&nbsp;false,&nbsp;this.SPWebPartManager,&nbsp;this.SPWebPartManager.Web, MarkupOption.None,&nbsp;false,&nbsp;false,&nbsp;refthis.frontPageWebPart,&nbsp;refthis.frontPageMarkupStorageKey,&nbsp;refthis.frontPageZoneId,&nbsp;refthis.frontPageWebPartImporter,&nbsp;refthis.frontPageRegisterDirectiveList,&nbsp;refthis.frontPageServerDocumentDesigner); -->&nbsp;2
&nbsp; &nbsp; &nbsp;this._errorText = partPreviewAndPropertiesFromMarkup.Error;
&nbsp; &nbsp; &nbsp;if&nbsp;(this.frontPageWebPart !=&nbsp;null)
&nbsp; &nbsp; &nbsp;{
&nbsp; &nbsp; &nbsp;&nbsp;this.ProcessFrontPagePart(this.frontPageWebPart);
&nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; }
&nbsp; &nbsp; IL_0111:
&nbsp; &nbsp;&nbsp;this._selectedWebPart =&nbsp;this.frontPageWebPart;
&nbsp; &nbsp;}
&nbsp; &nbsp;elseif&nbsp;(!this.InCustomToolPane)
&nbsp; &nbsp;{
&nbsp; &nbsp;&nbsp;if&nbsp;(this.SPWebPartManager.ForWebPartRender)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp;if&nbsp;(this.SPWebPartManager.WebParts.Count >&nbsp;0)
&nbsp; &nbsp; &nbsp;{
&nbsp; &nbsp; &nbsp;&nbsp;this._selectedWebPart =&nbsp;this.SPWebPartManager.WebParts[0];
&nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; &nbsp;else
&nbsp; &nbsp; &nbsp;{
&nbsp; &nbsp; &nbsp;&nbsp;this._selectedWebPartSet =&nbsp;false;
&nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;else
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp;this._selectedWebPart =&nbsp;this.SPWebPartManager.FindWebPartByEitherID(ToolPane.GetSelectedWebPartId(this.Page));
&nbsp; &nbsp; }
&nbsp; &nbsp;}
&nbsp; }
returnthis._selectedWebPart;
&nbsp;}
}

此方法用于获取并返回当前在 ToolPane 中被选中的 WebPart 对象实例,获取 WebPart 实例是通过 GetPartPreviewAndPropertiesFromMarkup。到此为止,就已经知道了公开PoCMSOTlPn_UriMSOTlPn_DWP的出处了:对应着SelectedAspWebPart方法中的代码12了。分析至此,漏洞的原理已经很清楚了:先通过Referer绕过鉴权,然后构造恶意的MSOTlPn_DWP数据,实现反序列化RCE。笔者觉得自己又行了,ysoserial.net一把梭就完了,但是在后续漫长的复现过程中才发现自己的无知!

失败的漏洞复现

对于已经公开的漏洞,笔者的习惯都是直接先用PoC进行复现,复现成功后再通过PoC去看具体的细节。但当笔者随机选取了一个PoC进行复现时,竟然出现了如下吊诡的结果,竟然返回401

笔者顿感大事不妙,开始梳理可能出现问题的地方,想到了以下四点:

  1. 认证绕过是否只对某些认证配置有效?这里涉及到SharePoint的几种认证方式,感兴趣的可以看看Jang的文章[2]
  2. 是不是SharePoint偷偷摸摸的自动升级了或者自动下发了IIS Rewrite规则,导致漏洞无法利用成功?
  3. 是不是与Defender或者AMSI有关?毕竟微软的东西确实是太复杂了
  4. 是不是PoC不对劲?

笔者按照上面的顺序,逐个排查,具体排查过程这不讲了,无非就是不停的尝试,最终定位到是PoC不对劲,这个时候其实已经没有什么好办法,只能硬着头皮单步调试了。但又因为一个请求利用了两个漏洞,增加了额外的噪音,所以笔者直接使用已登录的Cookie进行对反序列化的调试。在开始盲目的调试之前,先假设如果自己是一个研发,会怎么设计WebPart实例化过程,正常来说肯定会遵循下面的逻辑:

  1. 校验用户传入的数据是否是一个符合规范的WebPart数据,如果不是则抛出异常;如果是则进行实例化
  2. 实例化过程就是对WebPart数据进行反序列化的过程,如果在反序列化中发生错误,则抛出异常

由此,可以推断出PoC可能存在的问题点有两个:

  1. MSOTlPn_DWP并不是一个正确的WebPart格式,这种情况下无法运行到反序列化处,直接抛出异常返回
  2. 反序列化链构造错误,不符合WebPart反序列化的流程,在这种情况下能运行到反序列化处,但会在反序列化时抛出异常,但抛出的异常与第一个相同

以上两种情况,笔者都遇到过,但由于篇幅原因(笔者太懒),这篇先简单提一下反序列化链构造错误这种情况。根据公开信息很容易定位到真正反序列化的触发点在Microsoft.PerformancePoint.Scorecards.Helper.GetObjectFromCompressedBase64String方法,直接上DnSpy

抛出的异常显而易见,就是序列化数据的问题,先看GetObjectFromCompressedBase64String方法:

public&nbsp;static&nbsp;object&nbsp;GetObjectFromCompressedBase64String(string&nbsp;base64String)
{
if&nbsp;(base64String ==&nbsp;null&nbsp;|| base64String.Length ==&nbsp;0)
&nbsp;{
returnnull;
&nbsp;}
object&nbsp;obj =&nbsp;null;
byte[] array = Convert.FromBase64String(base64String);
using&nbsp;(MemoryStream memoryStream =&nbsp;new&nbsp;MemoryStream(array))
&nbsp;{
&nbsp; memoryStream.Position =&nbsp;0L;
&nbsp; GZipStream gzipStream =&nbsp;new&nbsp;GZipStream(memoryStream, CompressionMode.Decompress);
&nbsp; BinaryFormatter binaryFormatter =&nbsp;new&nbsp;BinaryFormatter();
&nbsp; obj = binaryFormatter.Deserialize(gzipStream);
&nbsp;}
return&nbsp;obj;
}

序列化数据得经过三次处理:BinaryFormatter->gzip->base64encode,但具体的细节留到下一篇再讲,一篇文章如果字太多笔者懒得写,读者也不乐意看。

一些武器化小笔记

漏洞复现和分析枯燥无味,下面简单说一些大家都感兴趣的东西,关于SharePoint武器化的一些notes

  1. 可以通过SharePoint返回头中的MicrosoftSharePointTeamServices字段判断SharePoint版本,做初步版本匹配是否存在漏洞,写扫描器的读者可以关注下
  2. 除了可以使用上篇中提到的/_layouts/SignOut.aspx/_layouts/15/SignOut.aspx绕过鉴权,还可以使用/_layouts/./SignOut.aspx
  3. 可通过写入WebShell获取MachineKey,实现持久化
  4. 并不是所有利用成功的请求都会返回200,有很大一部分但是利用成功的请求会返回401

写在后面

相比起复现漏洞,笔者认为这个漏洞链的利用手法更值得学习,希望下一篇能讲到!漏洞本身没有太多意义,好的利用才是漏洞中最令人心醉的部分。

Ref

  1. https://securelist.com/toolshell-explained/117045/
  2. https://testbnull.medium.com/sharepoint-not-so-0day-how-ive-failed-at-p2o-vancouver-2024-f6476745d397

查看原文:《浅谈SharePoint漏洞之CVE-2025-53770分析失败篇》

评论:0   参与:  2