postMessage安全漏洞分析与防护实践

微软安全响应中心深入研究了postMessage实现中的安全风险,发现了多个高影响漏洞,包括跨租户令牌泄露和XSS攻击,并分享了具体案例和修复方案。

postMessage与安全风险

在微软,保护生态系统不仅仅意味着修复错误,还包括主动寻找变体类别、识别系统性弱点,并在攻击者有机会之前跨团队合作保护客户。本篇博客重点介绍了这样一项工作:深入研究了微软服务中错误配置的postMessage处理程序的风险,以及MSRC如何与工程团队合作缓解这些风险。

这项研究是MSRC持续计划的一部分,旨在识别和消除共享域(即用于信任关系的广泛验证域)中的令牌泄露向量。它导致了多个高影响漏洞的发现——有些是跨租户的,有些涉及令牌转发,所有漏洞都源于过度宽松的信任模型。好消息是:我们已经实施了缓解措施并采取了降低风险的步骤,现在已为客户提供了保护。

理解postMessage及其重要性

现代Web应用程序依赖postMessage进行安全的跨源通信——在父页面和iframe之间,或弹出窗口与其打开者之间。当正确实现时,它能够在Teams、Power BI和Dynamics 365等服务中实现丰富的交互体验。但是当源验证缺失或配置错误时,postMessage就成为令牌盗窃、跨站脚本(XSS)和权限提升的强大向量。

postMessage API允许不同Window对象之间的安全跨源通信,例如在网页和其中嵌入的iframe之间,或在页面和它生成的弹出窗口之间。这是通过指定目标窗口和目标窗口必须具有的源来实现的:

语法概述

1
targetWindow.postMessage(message, targetOrigin);
  • targetWindow:对要发送消息的窗口的引用(例如,iframe的contentWindow或弹出窗口的window对象)
  • message:要发送的数据(可以是字符串或结构化对象)
  • targetOrigin:关键的安全参数,指定接收窗口的预期源。这确保仅当目标窗口匹配指定源时才传递消息

不安全源检查的隐藏危险

实现postMessage时最常见的失误之一是未能验证消息发送者或接收者的源。这可以通过两种关键方式体现:

不安全发送者:应用程序在不验证接收者源的情况下发送敏感数据(如身份验证令牌),可能将这些数据泄露给恶意框架。

1
targetWindow.postMessage(message,'*');

不安全监听器:在不验证发送者源(event.origin)的情况下接受消息的监听器容易受到伪造消息的攻击,这可能触发未经授权的操作或数据操纵。

例如,不安全的正则表达式验证:

1
2
3
4
5
6
window.addEventListener('message', (event) => {
    const regex = /^https:\/\/.*\.example\.com$/;
    if (regex.test(event.origin)) {
        // 处理消息
    }
});

在本博客后面,我们将探讨通过MSRC发现和报告的几个漏洞。

Window对象验证:错误的安全感

1
2
3
4
window.addEventListener("message", function (event) {
    // 不安全:信任iframe引用,而不是源
    if (event.source === trustedIframeWindow) {
        const message = event.data;

一个特别危险的反模式是验证event.source(窗口对象)而不是event.origin。虽然信任窗口对象可能看起来直观,但这种方法是有缺陷的。例如,考虑一个Web应用程序,它在iframe中嵌入了一个组件(例如数据可视化框架)。该组件使用postMessage与父应用程序通信。父应用程序注册一个消息处理程序,并尝试通过检查event.source与已知的iframe窗口对象来验证发送者——但它不验证event.origin。

这创建了一个漏洞。攻击者可以将整个应用程序嵌入到自己的页面中,通过将其src替换为恶意URL来劫持iframe,并获得对iframe窗口对象的控制。因为父应用程序只检查event.source,所以错误地信任了攻击者的消息。

通过缺失源验证的postMessage漏洞(targetOrigin ‘*’)

MSRC案例研究I:通过Bing Travel中的postMessage暴露身份验证令牌

我们在Bing Travel的JS文件中发现了以下代码:

1
2
3
4
// 代码检查this.recorderModalIframe及其contentWindow属性是否存在
// 如果存在,它使用postMessage方法向this.recorderModalIframe的contentWindow发送消息
// 发送的消息是一个对象{accessToken:this.userAuthJwt}
// postMessage调用中的"*"参数意味着消息将发送到任何窗口,无论其源是什么

通过在加载该脚本的页面中嵌入恶意iframe,每当触发postMessage交互时,iframe将能够拦截访问令牌。

缓解措施

为了解决由不安全的postMessage实现引起的令牌暴露风险,工程团队从其存储库中删除了受影响的包依赖项,并与上游包所有者协调以消除易受攻击的代码。这确保了问题在本地和源头上都得到解决,防止在依赖服务中再次发生。

MSRC案例研究II:通过web.kusto.windows.net中的postMessage暴露身份验证令牌

发现web.kusto.windows.net中的postMessage实现将一个包含敏感访问令牌的对象发送到Azure Marketplace iframe,使用了通配符targetOrigin("*")。该令牌具有user_impersonation范围。这种不安全的配置可能允许恶意iframe拦截消息并提取令牌和相关的clusterUrl,从而实现对用户资源的未经授权访问。

如果我们更仔细地分析响应中的代码:

1
2
3
4
// 代码通过调用l.dependencies中authProvider对象上的getToken来检索访问令牌
// 请求范围["dc78932e-f9b5-489b-b50a-f49d5831d9df/user_impersonation"]
// 该范围授予令牌为指定资源模拟用户的能力
// 一旦获得,令牌与clusterUrl一起嵌入到消息对象中,并通过postMessage发送到Azure Marketplace iframe

该令牌携带user_impersonation范围,这可能允许攻击者在接受此令牌的服务中代表用户行事。这为环境中的未经授权数据访问、权限提升和横向移动打开了大门。

缓解措施

在这种情况下,postMessage调用的严格可信源应该是https://${e}.kusto.windows.net/。该值已经用于构建消息负载中的clusterUrl。通过在postMessage的targetOrigin参数中使用相同的源,可以确保包含敏感访问令牌的消息仅传递到托管在特定Kusto集群端点的预期Azure Marketplace iframe。

为了完全缓解该问题,工程团队从代码库中删除了现已过时的Marketplace功能。这包括删除MarketplacePage组件、相关路由、菜单项、功能标志和多个文件中的本地化字符串。通过消除所有相关的代码路径,他们确保易受攻击的逻辑不再能被触发或重新引入。

当可信不安全时:postMessage中宽松源验证的风险

在现代云环境中,跨源通信是必不可少的——但当源验证过于宽泛时,它会为严重的安全风险打开大门。在Microsoft 365、Azure和Dynamics 365中反复出现的模式是使用服务级源验证(例如*.domain.com)而不是租户特定或应用特定的验证。虽然这简化了集成,但也创建了广泛的攻击面。

问题:过于宽泛的信任边界

宽松的源验证允许受信任服务内的任何子域通过postMessage()发送消息。如果这些子域中的一个被攻破,它可以用来向受信任的应用程序发送恶意消息,可能导致令牌盗窃、未经授权的操作或数据泄露。

真实世界示例

Microsoft 365

  • SharePoint Online:接受来自任何*.sharepoint.com域的消息意味着受感染的站点可能向受信任的应用程序注入恶意脚本。
  • Teams集成:第一方和第三方应用程序通常允许来自任何*.teams.microsoft.com域的消息,如果子域被攻破,则存在未经授权访问的风险。

Azure

  • Azure App Services:允许来自任何*.azurewebsites.net域的postMessage()可能导致XSS或数据盗窃,如果攻击者控制子域。
  • Azure Functions:当函数跨*.azurefunctions.net通信而没有严格源检查时,会出现类似风险。

Dynamics 365

  • 门户:信任所有*.dynamics.com域可能使内部应用程序面临来自受感染门户的恶意消息。
  • Power Apps:接受来自任何*.powerapps.com域消息的集成容易受到受感染或过于宽松的应用程序的滥用。

为什么会发生

这种模式通常源于支持动态、可扩展集成而无需不断更新源列表的愿望。但代价是削弱了攻击者可以利用的安全边界。

postMessage漏洞的3种利用技术

虽然postMessage是跨窗口通信的强大工具,但其误用——特别是在过度信任域的上下文中——可能导致严重的安全漏洞。MSRC已识别出攻击者可以在广泛信任域启用postMessage时使用的三种主要利用技术:

  1. 受信任域中的XSS:如果受信任域(例如*.contoso.com)容易受到跨站脚本(XSS)攻击,攻击者可以注入恶意脚本,利用postMessage通道泄露令牌或模拟用户。

  2. 接管悬空域:曾经有效但不再维护的域(悬空)可以被重新注册或劫持。如果这些域保留在Teams应用程序清单的validDomains列表中,它们将成为消息拦截或注入的静默后门。

  3. 设计上的自定义代码:一些平台,如Power Apps,有意允许用户嵌入自定义JavaScript。如果这些应用程序包含在validDomains中,它们可能被滥用以运行恶意代码,利用postMessage与特权Teams上下文交互。

受信任Microsoft Teams 1P应用程序中的postMessage漏洞

MSRC漏洞与缓解(V&M)团队发现了多个高影响安全问题,源于Microsoft Teams应用程序中过于宽松的postMessage配置。这些问题通常由两个因素组合引起:宽松的域验证和过度特权的应用程序清单设置。

变体狩猎的真实世界发现

  • MSRC案例研究III:在Teams的FileBrowser组件中发现了一个通过深层链接触发的1-click XSS漏洞。
  • MSRC案例研究IV:在Teams Meetings中的Power Virtual Agents中发现了一个0-click XSS(我们下面研究)。

在这两种情况下,根本原因涉及具有isFullTrust设置为true的Teams应用程序,允许它们使用postMessage()与主Teams窗口通信。这些应用程序还具有过于宽泛的validDomains列表,使得能够与可能被攻破或滥用的域进行跨窗口通信。

MSRC案例研究III – 通过Teams中的Power Virtual Agents的0-click XSS

重要的应用程序清单设置

每个Teams应用程序包括一个清单——一个定义其能力和受信任域的JSON文件。两个字段对postMessage安全特别相关:

  • validDomains:列出受信任用于postMessage、iframe嵌入和API调用的域。如果此列表包括通配符如*.microsoft.com*.powerapps.com,它会显著增加攻击面。
  • 跨窗口通信:应用程序只能使用postMessage()在主Teams窗口、会议窗口和弹出窗口之间通信,如果目标域列在validDomains中。

攻击向量:清单错误配置

攻击向量取决于Microsoft Teams应用程序中的两个关键清单设置:

  • isFullTrust: true:授予应用程序与主Teams窗口通信和访问身份验证令牌的权限。
  • validDomains:列出应用程序信任用于postMessage、iframe嵌入和API调用的域。

如果恶意行为者可以在列在validDomains中的域上托管代码,他们可以利用此信任来:

  • 拦截或注入postMessage流量。
  • 检索身份验证令牌。
  • 代表用户执行操作——可能跨租户。

当与XSS、悬空域或允许用户编写脚本的平台结合时,此向量特别危险。

可视化利用

利用利用了Teams中的窗口层次结构,其中嵌入式应用程序可以与主窗口通信,如果通过清单授予信任。通过将恶意iframe或脚本注入受信任域,攻击者可以启动消息交换,导致主机窗口发回访问令牌。

有效负载托管在:

1
https://copilotstudio.microsoft.com/environments/.../teamsExploit_fullTrust_FilesandDocs_graph.js

此URL被认为是可信的,因为它属于*.microsoft.com,该域列在Copilot Studio应用程序清单的validDomains中。托管的脚本(teamsExploit_fullTrust_FilesandDocs_graph.js)向Teams主机窗口发起postMessage请求,触发身份验证令牌的返回。

利用设置:应用程序共享操纵

为了触发利用,攻击者在Teams Meeting期间捕获合法的应用程序共享请求,并将contentSharing负载中的Identifier字段替换为base64编码的恶意JSON对象:

1
2
3
4
5
6
{
    "appId": "com.microsoft.teamspace.tab.virtualagent.app",
    "contentSharing": {
        "identifier": "base64编码的恶意负载"
    }
}

这里,appId对应于Power Virtual Agent的appId。通过将请求方法从PUT更新为POST,将API路径从updateEndpointMetadata更新为addModality,base64编码此负载并替换addModality请求中的标识符值,可以获得202 Accepted响应代码。

我们在受害者用户的会话中看到XSS警报,无需他们进行任何交互:

实现0-click XSS

结果是零点击跨站脚本(XSS)漏洞,它:

  • 执行攻击者控制的代码,无需任何用户交互。
  • 跨租户工作,意味着来自另一个组织的访客用户可以利用该漏洞。
  • 绕过基于UI的限制——虽然UI可能阻止访客用户启动应用程序共享,但后端逻辑不强制执行此限制,允许利用成功。

缓解措施

一些简单但有影响的更改可以显著减少攻击面:

应用程序级强化

  • 收紧域限制:避免过于宽泛的模式,如*.sharepoint.com*.powerapps.com。这些通配符条目可能无意中信任恶意或受感染的子域。

Teams特定控制

  • 限制完全信任:除非绝对必要,否则限制在Teams应用程序清单中使用isFullTrust。
  • 采用嵌套AAD身份验证:过渡到嵌套Azure Active Directory身份验证流可以帮助隔离和包含令牌访问。

安全提示:考虑利用内容安全策略(CSP)标头——特别是iframe-src——进一步约束应用程序可以嵌入的位置。

CVE-2024-49038:安全设计的关键时刻

此漏洞记录为CVE-2024-49038,被评为严重,CVSS评分为9.3/8.1。它突显了即使源验证中的小疏忽也可能导致严重安全风险。该事件清楚地提醒我们,为什么安全默认值和主动验证在现代应用程序开发中是必不可少的。

微软的迅速响应

微软迅速采取行动缓解受影响应用程序中的问题——现在称为Microsoft Copilot Studio。实施了以下更改:

  • 应用程序名称从"Power Virtual Agents"更新为"Microsoft Copilot Studio",聊天机器人重命名为代理。
  • validDomains列表被修订以删除先前允许过于宽松信任关系的通配符条目。
  • 开发人员URL和支持的语言被更新。
  • isFullTrust设置由隐私和安全团队审查和批准。
  • 强制执行了额外的清单卫生,包括将IsTeamsOwned设置为true,并确保为具有选项卡的应用程序配置showLoadingIndicator。

这些更改作为微软内部发布过程的一部分进行了审查,加强了安全默认配置和快速响应协议的重要性。

客户建议:减少攻击面

为了帮助客户主动保护其应用程序并降低postMessage漏洞的风险,我们建议以下操作:

  1. 审计应用程序清单:将validDomains限制为仅绝对必要的域。避免通配符如*.powerapps.com*.sharepoint.com,这些可能无意中扩大攻击面。

  2. 限制特权:仅在场景必需时在Teams应用程序清单中使用isFullTrust: true。

  3. 强制执行CSP标头:在内容安全策略中使用frame-ancestors和iframe-src指令,防止未经授权的嵌入和框架。

  4. 验证源:确保所有postMessage监听器根据严格允许列表检查event.origin,绝不单独依赖窗口对象引用。

  5. 删除未使用或悬空域:定期审查并从配置中删除任何过时的域,防止它们成为静默后门。

  6. 利用CodeQL和动态分析:使用CodeQL查询检测不安全的postMessage模式(如通配符targetOrigin值或缺失源验证),并将动态分析工具集成到构建管道中以早期捕获漏洞。

示例CodeQL查询供客户指导

查找具有通配符源的postMessage调用

此查询标记targetOrigin设置为"*“的postMessage使用——一种常见的反模式。

1
2
3
// 查找具有通配符targetOrigin的postMessage调用
postMessageCall
where postMessageCall.getArgument(1).getStringValue() = "*"

检测postMessage处理程序中缺失的源验证

此查询识别使用postMessage而不验证event.origin的JavaScript处理程序。

1
2
3
4
5
6
7
import javascript 

from EventHandler eh, Function f
where eh.getEventName() = "message" and
    eh.getAFunction() = f and
    not exists(f.getAChild().(IfStmt).getCondition().(BinaryExpr).getAnOperand().(PropertyAccess).getPropertyName() = "origin")
select f, "此消息处理程序不验证传入消息的源。"

有关更高级的查询和指导,请参阅https://codeql.github.com/docs/。

通过遵循这些建议,客户可以显著减少他们对postMessage相关漏洞的暴露,并帮助确保其应用程序默认安全。安全是共同责任,主动措施——结合微软自身的缓解措施——是在规模上保护用户的关键。

接近默认安全

保护现代云应用程序需要的不仅仅是修补单个错误——它需要向默认安全设计的系统性转变。在微软postMessage实现中发现的漏洞强调了即使善意的功能在信任边界过于宽泛或源验证应用不当时也可能成为攻击向量。

虽然微软工程团队(EG)承担消除不安全模式的主要责任——例如通配符targetOrigin值或过于宽松的validDomains——但客户可以采取一些步骤来保护自己。在客户托管自己的应用程序或门户的场景中,例如自定义Teams应用程序、Power Apps门户或嵌入式微软服务,他们可以强制执行严格的内容安全策略(CSP)标头,如frame-ancestors和iframe-src,以防止未经授权的嵌入和框架。他们还可以审计自己的应用程序清单,避免使用isFullTrust: true除非绝对必要,并确保所有postMessage监听器验证event.origin。一些预防措施还包括作为SDL实践的一部分检查XSS漏洞的存在,并删除未使用的子域。

然而,对于微软托管的1P应用程序——如Teams、SharePoint Online或Copilot Studio——客户无法控制CSP标头或清单配置。在这些情况下,责任在于微软强制执行安全默认值,包括删除通配符域条目、收紧源验证和最小化过度特权清单设置的使用。虽然将安全默认值构建到我们的SDL过程中,错误仍可能发生。作为最佳实践,应执行安全测试,并在发现安全错误时遵循响应过程。MSRC团队的一部分,特别是漏洞和缓解团队,进行此类测试研究,以在客户受到伤害之前发现和缓解这些漏洞。该团队定期打开内部MSRC案例以解决我们发现的漏洞。CVE-2024-49038的迅速缓解,涉及删除通配符域和修订Microsoft Copilot Studio中的应用程序清单设置,体现了保护规模用户所需的决定性行动。

最终,接近默认安全意味着将安全性嵌入到每个应用程序和服务的架构中——不是事后想法,而是基本原则。它需要跨工程、安全和客户团队的协作,以确保信任是赢得的,而不是假定的,并且每条消息、令牌和源都受到应有的审查。

致谢

特别感谢Michael Hendrickx、Rebecca Pattee、Jeremy Tinder和Evan Grant(@stargravy)对本博客的贡献,以及本篇博客中描述的各种微软团队的工作。

Jhilakshi Sharma 安全研究员,MSRC

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计