导语:近日,Microsoft发布了一个补丁程序,以修复 CVE-2020-1181 的Microsoft SharePoint Server版本中的远程代码执行漏洞,此文章更深入地研究了此漏洞的根本原因。
近日,Microsoft发布了一个补丁程序,以修复 CVE-2020-1181 的Microsoft SharePoint Server版本中的远程代码执行漏洞,此文章更深入地研究了此漏洞的根本原因。
在此补丁程序可用之前,SharePoint Server允许经过身份验证的用户在SharePoint Web应用程序服务帐户的上下文和权限中在服务器上执行任意.NET代码。为了使攻击成功,攻击者应在SharePoint网站上具有“ 添加和自定义页面”权限。但是,SharePoint的默认配置允许经过身份验证的用户创建网站。当他们这样做时,用户将是该网站的所有者,并将拥有所有必要的权限。
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-1181
0x01 漏洞描述
Microsoft SharePoint Server允许用户创建网页,但是为了防止被利用,它对可以在这些页面上显示的组件进行了严格限制。SharePoint服务器以不同的方式处理其“自己的”页面和用户定义的页面。SharePoint的“自有”页面存储在文件系统上,不受所有限制。用户页面存储在数据库中,并且受到限制。其中一些限制包括无法使用代码块或包含文件系统中的文件。他们通常只能使用预定义列表中允许的Web控件。
如果用户通过上传创建新页面,则该页面将照常受到限制。但是,如果新页面是通过SharePoint Web编辑器创建的,则它将被视为受信任的源。这是有道理的,因为SharePoint Web编辑器对可以添加到页面中的组件进行了限制,因此可以在不受限制的模式下安全地运行页面。
发生此漏洞的原因是编辑器允许的一种Web部件类型WikiContentWebpart,并且该Web部件允许包含任意ASP.NET标记。这为攻击者提供了一种以不受限制的方式运行任意ASP.NET标记的途径,从而导致远程执行代码。
0x02 漏洞代码
SharePoint用于SPPageParserFilter阻止所有危险内容,回顾一下如何初始化SPPageParserFilter:
// Microsoft.SharePoint.ApplicationRuntime.SPPageParserFilter protected override void Initialize() { if (!SPRequestModule.IsExcludedPath(base.VirtualPath, false)) { this._pageParserSettings = SPVirtualFile.GetEffectivePageParserSettings(base.VirtualPath, out this._safeControls, out this._cacheKey, out this._isAppWeb); this._safeModeDefaults = SafeModeSettings.SafeModeDefaults; return; } /* ... */ } // Microsoft.SharePoint.ApplicationRuntime.SPVirtualFile internal static PageParserSettings GetEffectivePageParserSettings(string virtualPath, out SafeControls safeControls, out string cacheKeyParam, out bool isAppWeb) { HttpContext current = HttpContext.Current; SPRequestModuleData requestData = SPVirtualFile.GetRequestData(current, virtualPath, true, true); SPVirtualFile webPartPageData = requestData.GetWebPartPageData(current, virtualPath, true); return webPartPageData.GetEffectivePageParserSettings(current, requestData, out safeControls, out cacheKeyParam, out isAppWeb); } // Microsoft.SharePoint.ApplicationRuntime.SPDatabaseFile internal override PageParserSettings GetEffectivePageParserSettings(HttpContext context, SPRequestModuleData basicRequestData, out SafeControls safeControls, out string cacheKeyParam, out bool isAppWeb) { PageParserSettings pageParserSettings = this.PageParserSettings; isAppWeb = this._isAppWeb; safeControls = null; cacheKeyParam = null; if (pageParserSettings == null) { if (this.IsGhosted) { bool treatAsUnghosted = this.GetTreatAsUnghosted(context, basicRequestData, this.GetDirectDependencies(context, basicRequestData)); if (!treatAsUnghosted) { treatAsUnghosted = this.GetTreatAsUnghosted(context, basicRequestData, this.GetChildDependencies(context, basicRequestData)); } if (treatAsUnghosted) { pageParserSettings = PageParserSettings.DefaultSettings; } else if (this._isAppWeb) { pageParserSettings = PageParserSettings.GhostedAppWebPageDefaultSettings; } else { pageParserSettings = PageParserSettings.GhostedPageDefaultSettings; } } else { pageParserSettings = PageParserSettings.DefaultSettings; } } if (!pageParserSettings.AllowUnsafeControls) { safeControls = this.SafeControls; } cacheKeyParam = this.GetVirtualPathProviderCacheKey(context, basicRequestData); return pageParserSettings; }
如果我们使用SharePoint Web Editor创建页面,则该页面将具有IsGhosted = true并且_isAppWeb将设置为false。请注意,还有一项额外的检查以确保没有较低信任级别的依赖项文件:
// Microsoft.SharePoint.ApplicationRuntime.SPDatabaseFile private bool GetTreatAsUnghosted(HttpContext context, SPRequestModuleData requestData, System.Collections.ICollection dependencyVirtualPaths) { bool result = false; foreach (string path in dependencyVirtualPaths) { SPDatabaseFile sPDatabaseFile = requestData.GetWebPartPageData(context, path, true) as SPDatabaseFile; if (sPDatabaseFile != null && !sPDatabaseFile.IsGhosted && (sPDatabaseFile.PageParserSettings == null || sPDatabaseFile.PageParserSettings.CompilationMode != CompilationMode.Always)) { result = true; break; } } return result; }
但是,我们尚未添加任何此类文件,因此应该在这里通过并通过此检查。结果,GetEffectivePageParserSettings()将返回PageParserSettings.GhostedPageDefaultSettings:
// Microsoft.SharePoint.ApplicationRuntime.PageParserSettings internal static PageParserSettings GhostedPageDefaultSettings { get { if (PageParserSettings.s_ghostedPageDefaultSettings == null) { PageParserSettings.s_ghostedPageDefaultSettings = new PageParserSettings(CompilationMode.Always, true, true); } return PageParserSettings.s_ghostedPageDefaultSettings; } } // Microsoft.SharePoint.ApplicationRuntime.PageParserSettings internal PageParserSettings(CompilationMode compilationmode, bool allowServerSideScript, bool allowUnsafeControls) { this.m_compilationMode = compilationmode; this.m_allowServerSideScript = allowServerSideScript; this.m_allowUnsafeControls = allowUnsafeControls; }
其结果是,我们的页面都会有compilationmode=Always,allowServerSideScript=true和allowUnsafeControls=true。现在让我们仔细看看WikiContentWebpart:
// Microsoft.SharePoint.WebPartPages.WikiContentWebpart protected override void CreateChildControls() { if (!this.Visible || this.Page == null) { return; } if (this.Page.AppRelativeVirtualPath == null) { this.Page.AppRelativeVirtualPath = "~/current.aspx"; } Control obj = this.Page.ParseControl(this.Directive + this.Content, false); this.AddParsedSubObject(obj); }
这意味着其参数(Directive和Content)中的内容将由ParseControl(text2, false)解析。第二个参数(false)将强制使用PageParserFilter,但将与PageParserSettings.GhostedPageDefaultSettings一起使用。
因为该ParseControl()方法永远不会被编译,所以我们不能直接指定.NET代码。但是,我们可以在SharePoint中使用危险控件来调用任意方法并执行代码。WikiContentWebpart是将运行任意OS命令的配置示例:
0x03 漏洞验证
在我们的演示中,我们使用了Microsoft SharePoint 2019 Server,并在Windows Server 2019 Datacenter版系统上安装了所有默认选项。我们为其分配了名称sp2019.contoso.lab,并使其成为contoso.lab域的成员。我们的域控制器位于单独的虚拟机上。到2020年2月,我们的目标计算机已安装了所有可用的补丁程序,其版本为16.0.10355.20000。
我们的攻击者系统仅需要任何受支持的Web浏览器。在下面的截图中,我们使用了Mozilla Firefox 69.0.3。我们还将使用类似于上面示例的WikiContentWebpart自定义,将其命名为 WikiContentRCE.xml 。
访问我们的SharePoint Server并以普通用户身份进行身份验证。在此示例中,它是 user2 :
创建一个站点,以便成为所有者并拥有完全权限。
点击顶部面板上的“ SharePoint ”:
点击 “ +创建网站 ” 链接:
选择 团队站点。现在,我们需要为新站点选择一个名称。在此示例中,它是 testsiteofuser2。
单击“ 完成 ”,将创建新站点:
现在,我们单击“ 页面” 链接:
我们需要切换到 经典视图。为此,只需单击左下角的“ 返回经典SharePoint ”链接:
单击“ +新建 ”,然后为我们的新页面选择任何名称。在此示例中,我们将其称为 newpage1:
单击 创建 按钮进行确认。
现在我们需要 在“ 插入” 选项卡上选择“ Web部件 ” :
在对话框窗口中,选择左下角的“ Upload Web Part ”链接,然后上传精心制作的 WikiContentRCE.xml文件:
点击 上传。你可能会收到一个弹出警告,指出:“ 此页面要求你确认要离开-输入的数据可能不会保存。” 只需单击“ 离开页面 ”按钮进行确认。然后,我们返回到主编辑视图:
我们需要 再次 在INSERT选项卡上选择 Web部件小部件 。它将具有我们导入的精心制作的Web部件:
在单击“ 添加” 按钮之前,让我们转到目标SharePoint服务器并打开 C:\ windows \ temp 文件夹:
请注意,没有 RCE_PoC.txt 文件。
现在,让我们回到攻击者机器上,并将导入的Web部件添加到页面中:
再次检查目标服务器上的 C:\ windows \ temp文件夹:
这样,我们的攻击者可以执行任何OS命令并破坏服务器。他们只需要用echo pwned > c:/windows/temp/RCE_PoC.txt所需的命令替换WikiContentRCE.xml文件中的字符串。
0x04 分析总结
Microsoft在其补丁程序文档中将此漏洞的漏洞利用指数(XI)分级为2,这意味着他们认为不太可能利用此漏洞。但是,正如我们在PoC部分中所展示的那样,对于任何经过身份验证的用户而言,利用此漏洞都是非常简单的。因此,我们建议将XI视为1,这表明有可能被利用。根据Microsoft的说法,他们通过“更正Microsoft SharePoint Server处理已创建内容的方式”修补了该漏洞。在这种情况下,这似乎是一条合理的途径。SharePoint仍然是研究人员和攻击者的有吸引力的目标,并且我们即将发布的一些与SharePoint相关的漏洞披露。
本文翻译自:https://www.zerodayinitiative.com/blog/2020/6/16/cve-2020-1181-sharepoint-remote-code-execution-through-web-parts如若转载,请注明原文地址