前言
几天前,Orange在他的BlackHat演讲中又曝出了两条Microsoft Exchange攻击链,即ProxyOrcale和ProxyShell,前者主要用于Padding Orcale攻击,后者则利用路径混淆漏洞实现任意文件写入并最终执行代码。
本文假设读者已经阅读了Orange的幻灯片,并对ProxyLogon具有基本的了解。
另外,请注意,出于显而易见的原因,这里不会公开相应的exploit。相反,本文旨在分享我在复现RCE漏洞和编写exploit方面的经验。
漏洞链
SSRF
这个攻击是从利用一个SSRF漏洞开始的,该漏洞是由于一些奇怪的URI解析造成的路径混乱所致。
PowerShell端点
然后,通过访问内部网络,我们可以尝试访问/powershell端点,从而实现与Exchange PowerShell之间的通信。请注意,由于Exchange的Powershell环境的缘故,我们可以运行的命令是非常有限的。
通过利用SSRF漏洞,我们就能以NT Authroity/System的身份来访问/powershell端点。这在正常情况下是没有问题的,但在这种情况下,由于无法识别,因此无法通过身份验证。因此,我们需要降低我们的权限来获得对端点的访问权限。
该端点需要一个名为X-CommonAccessToken的HTTP头部,但我相信Exchange不会将这个头部转发到内部后端服务器。然而,我们可以通过另一种方式获得令牌:通过提供一个名为X-Rps-CAT的GET参数,其中存放令牌内容;之后,它将被反序列化并添加为X-CommonAccessToken。
执行PowerShell命令
有了验证自己身份的方法,我们就可以更进一步,尝试实现代码执行。正如我之前所说,由于当前环境下会受到各种限制,因此,我们可以利用的东西很少。但是,却存在这样一个命令:通过它,我们能够在机器的任何位置写入文件……但是,文件只能是PST格式。
但是,PST文件并不是把所有的内容都放以明文形式存放,相反,该格式会将文件内容进行编码,这在微软的官方文件中是有介绍的。
所以,我们可以先对payload进行编码,然后,在生成并被编码PST文件时,会对payload再次进行编码,这将使最终结果保持不变。
同时,因为我们可以提供一个网络共享,这意味着我们可以直接告诉Exchange导出文件到\127.0.0.1\C$\pathto\shell。这个webshell虽然不是很优雅,但的确能用。
但在这之前,我们需要让我们控制的用户拥有导出邮件的权限。为此,我们需要借助于New-ManagementRoleAssignment,我们可以通过它把邮箱导入导出的角色分配给用户。
发送Payload
解决了这个问题后,我们就可以设法发送有效载荷。由于我们将利用New-MailExportRequest,所以,我们需要向要导出的邮箱里发送一封包含有效载荷的邮件。
我发现,有两种方法可以解决这个问题。其中,一种方法是Orange使用的方法,发送一封邮件到地址,然后导出它。另一种方法,是Peter和Jang在Peter的博客中提出的方法:使用EWS来冒充用户,并将包含有效负载的草稿保存为附件。
我决定采取第二种方法,因为它更加方便。
复现漏洞
在上面的章节中,我已经解释了攻击链的基本概念,现在是时候进行复现了。
SSRF部分其实并不重要,它只是后面所有攻击的入口,但本身并没有什么技术上的挑战。
构造CommonAccessToken
所以,我们需要一个有效的令牌,但真正的问题是:怎样才能获得一个有效的令牌呢?
为了抓去一个令牌示例,我设置了一些断点,拦截了Exchange内部发送的一些请求。最后,得到了下面这样一个令牌:
X-CommonAccessToken:VgEAVAdXaW5kb3dzQwBBCEtlcmJlcm9zTBZGXEhlYWx0aE1haWxib3g3ZjRiOTM1VS1TLTEtNS0yMS0xOTU2NzE2NjYxLTMwNzcyMTY4MjctMzc2OTU5MzkzLTExMzVHBgAAAAcAAAAsUy0xLTUtMjEtMTk1NjcxNjY2MS0zMDc3MjE2ODI3LTM3Njk1OTM5My01MTMHAAAAB1MtMS0xLTAHAAAAB1MtMS01LTIHAAAACFMtMS01LTExBwAAAAhTLTEtNS0xNQcAAAAIUy0xLTE4LTFFAAAAAA==
很明显,这里使用了base64编码;在解码之后,我决定利用hexdump软件进行观察,因为其中含有数百万个不可打印的字符。
从hexdump中,我们可以看到一些关键字符串:Kerberos、Windows、usernames,以及一些SID。此外,还有一些以单个字母作为前导的数据,这里假设其结构为:Type-Length-Value。
我对一些DLL进行了反汇编,以考察令牌是如何被序列化和反序列化的,并获得一些大致的想法。其中,最令人感兴趣的DLL是Microsoft.Exchange.Security.Authorization.dll。
在Deserialize函数中,我们可以看到V代表版本,T代表类型,C代表压缩的意思。
而E则代表扩展数据的意思。
在WindowsAccessToken中,我们可以找到额外的信息。
其中,A代表认证类型,L代表登录用户,U代表用户SID,最后,G代表组SID。
现在,我们已经知道了令牌的格式,接下来,我们将尝试构建自己的令牌。但现在问题出现了:我们需要一个SID和组SID,对吧?
如果您关注过ProxyLogon的漏洞,就会知道如何去做了。我们可以通过发送一个请求给https://exchange/autodiscover/autodiscover.xml,首先获得传统的域名,然后,使用传统的域名,通过发送另一个请求给https://exchange/mapi/emsmdb/,来找到用户的SID。
那么组SID呢?嗯,在Windows中,用户的SID是唯一的,但组的SID却不是。例如,对于管理员组中的用户,其组SID是S-1-5-32-544。顺便说一下,普通用户的组SID是S-1-5-32-545。
所以,我们基本上掌握了所需的全部信息,那么,我们该如何构建一个令牌呢?对于版本、类型、压缩、授权方面,我们可以保持原样。实际上,我们只需要改变登录名、用户SID以及组SID。
下面是用来生成令牌的部分代码:
我并没有真正弄明白为什么组SID后面总是跟着\x00\x00\x07,在这一点上我真的太懒了。而且,这也不是很重要。
我们已经成功伪造了自己的令牌,现在是时候测试一下,看看能否访问Powershell端点了。
如果响应代码是200,这意味着令牌已经被接受,我们大功告成。否则,则可能需要进行一些调试……
使用远程Powershell
下一个大任务是实现与Powershell端点的通信。实际上,该端点是通过WSMan协议的Powershell进行远程通信的。而WSMan是一个基于HTTP与SOAP XML的协议,如果我们自己动手实现该协议的话,将是一件非常痛苦的事情。
但我们很幸运,有人已经完成了这项艰巨的工作。因为我们可以利用Python库PyPSRP来完成相应的工作。
不过,我们还有一件事需要处理。由于WSMan是直接与目标服务器进行对话的,也就是说,很可能是通过HTTP与exchange:5985端口进行通信的。但我们的情况有点不同。我们需要它与Powershell端点进行通信,而不是其他端口。那么,我们如何实现这一点呢?
首先,我想看看请求是什么样子的,所以,我在自己的机器上设置了一个本地监听器,并发送了一个WinRM请求。在这个测试过程中使用的代码如下所示:
实际上,username、password和auth段其实并不重要,因为这个请求不会发往外部。相反,我们只是想让它发送至127.0.0.1:8080端口,用于测试。
事实证明,这个请求并没有多大区别,我们只需要改变XML数据中的主机字段和一些URI数据即可。
但是,具体该怎么做呢?由于PyPSRP并不支持这种东西,所以,我希望借助于burp,这时我产生了一个想法:我可以为WinRM实现一个本地HTTP代理服务器。
下面是我画的一个简单的示意图:
下面给出HTTP服务器的代码:
在编写exploit时遇到的另一个障碍是线程问题。因为执行到http.service_forever()的时候,服务器就停止了;经过一番研究,我想到一个办法:在另一个线程中启动服务器,这样就好了。
我还想指出,在执行Powershell命令时,一定要进行必要的清理工作,比方说,删除导出请求记录。实际上,有一个Remove-MailboxExportRequest命令,可以用来删除这些记录。
发送Payload
我们需要向Exchange Web Service(EWS)发送一个XML请求,以创建一个带有payload附件的电子邮件草稿。为了节省读者的时间,因为我已经花了一天的时间在这上面,这里直接给出相应的XML模板。它是在Peter提供的payload的基础上改造而成的。
下一个部分代码中含有我们的payload,它实际上就是一行ASPX webshell命令:
下一部分代码,将对payload进行编码,所以,当PST再次进行编码时,将恢复为明文形式的payload。
在微软的页面上,我稍微修改了一下代码,编译并保存了二进制数据,然后对其进行了base64编码。
#include < stdio.h >
#include < windows.h >
#include < string.h >
byte mpbbCrypt[] =
{
65, 54, 19, 98, 168, 33, 110, 187,
244, 22, 204, 4, 127, 100, 232, 93,
30, 242, 203, 42, 116, 197, 94, 53,
210, 149, 71, 158, 150, 45, 154, 136,
76, 125, 132, 63, 219, 172, 49, 182,
72, 95, 246, 196, 216, 57, 139, 231,
35, 59, 56, 142, 200, 193, 223, 37,
177, 32, 165, 70, 96, 78, 156, 251,
170, 211, 86, 81, 69, 124, 85, 0,
7, 201, 43, 157, 133, 155, 9, 160,
143, 173, 179, 15, 99, 171, 137, 75,
215, 167, 21, 90, 113, 102, 66, 191,</