深入剖析postMessage安全风险:微软生态中的令牌窃取与跨站脚本漏洞

本文详细探讨了微软服务中因postMessage处理程序配置不当引发的安全风险,包括多个高影响漏洞案例,如跨租户令牌窃取、令牌转发及XSS漏洞,并提供了具体的缓解措施和防护建议。

postMessaged and Compromised

      MSRC

/

    By Jhilakshi Sharma

/

    August 25, 2025

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

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

理解postMessage及其重要性

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

postMessage API允许不同Window对象(例如网页与其内部嵌入的iframe之间,或者页面与其打开的弹出窗口之间)进行安全的跨源通信。这是通过指定目标窗口以及目标窗口为接收消息必须具备的来源(origin)来实现的:

语法概述

targetWindow.postMessage(message, targetOrigin);

  • targetWindow:你想要发送消息的目标窗口的引用(例如,iframe的contentWindow或弹出窗口的window对象)。
  • message:你想要发送的数据(可以是字符串或结构化对象)。
  • targetOrigin:一个关键的安全参数,指定了接收窗口的预期来源。这确保了只有当目标窗口与指定的来源匹配时,消息才会被传送。

不安全的来源检查带来的隐藏危险

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

  1. 不安全的发送者:应用程序在不验证接收者来源的情况下发送敏感数据(如身份验证令牌),会导致数据泄露给恶意框架。 targetWindow.postMessage(message, '*');
  2. 不安全的监听器:在不验证发送者来源(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。虽然信任窗口对象看起来直观,但这种方法是有缺陷的。例如,考虑一个在iframe中嵌入组件(例如数据可视化框架)的Web应用程序。该组件使用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
// 此处原文中代码为HTML注释包裹,实际应为JS代码
if (this.recorderModalIframe && this.recorderModalIframe.contentWindow) {
    this.recorderModalIframe.contentWindow.postMessage({accessToken:this.userAuthJwt}, "*");
}

脚本检查this.recorderModalIframe及其contentWindow属性是否存在。如果存在,则使用postMessage方法向this.recorderModalIframecontentWindow发送消息。发送的消息是一个对象{accessToken:this.userAuthJwt}

postMessage调用中的"*“参数意味着消息将被发送到任何窗口,无论其来源是什么。通过在加载该脚本的页面中嵌入恶意iframe,当postMessage交互被触发时,该iframe将能够截获访问令牌。

缓解措施

为了解决因不安全的postMessage实现导致的令牌泄露风险,工程团队从其代码仓库中移除了受影响的包依赖,并与上游包所有者协调以消除易受攻击的代码。这确保了问题在本地和源头都得到解决,防止了相关服务中再次出现此问题。

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

1
2
3
4
5
6
7
// 代码片段
{
    // ... 获取令牌
    const token = await l.dependencies.authProvider.getToken([dc78932e-f9b5-489b-b50a-f49d5831d9df/user_impersonation]);
    // ... 发送消息
    c.contentWindow.postMessage({accessToken: token.accessToken, clusterUrl: `https://${e}.kusto.windows.net/`}, "*");
}

在web.kusto.windows.net中发现的一个postMessage实现被发现会向Azure Marketplace iframe发送一个包含敏感访问令牌(作用域为user_impersonation)的对象,并且使用了通配符targetOrigin("*")。这种不安全的配置可能允许恶意iframe拦截消息并提取令牌和关联的clusterUrl,从而导致对用户资源的未授权访问。为了缓解此问题,postMessage调用应指定从clusterUrl派生的确切受信任来源。

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

1
2
// 详细代码分析
// 代码通过调用l.dependencies.authProvider上的getToken来获取访问令牌,请求作用域["dc78932e-f9b5-489b-b50a-f49d5831d9df/user_impersonation"]。该作用域授予令牌为指定资源(由GUID标识)模拟用户的能力。获取后,令牌被嵌入到一个消息对象中——连同clusterUrl——并通过postMessage发送到Azure Marketplace iframe。

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

缓解措施
1
2
3
// 理想中,应该严格指定目标来源
const targetOrigin = `https://${e}.kusto.windows.net/`;
c.contentWindow.postMessage({accessToken: token.accessToken, clusterUrl: targetOrigin}, targetOrigin);

在这种情况下,postMessage调用的严格受信任来源应该是https://${e}.kusto.windows.net/。这个值已经在上面的代码片段的第18行中用于构造消息负载中的clusterUrl。通过在postMessagetargetOrigin参数中使用相同的来源,可以确保包含敏感访问令牌的消息仅传送到托管在该特定Kusto集群端点上的目标Azure Marketplace iframe。

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

当受信任不再安全:postMessage中宽松来源验证的风险

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

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

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

实际示例

1
2
3
4
5
6
// 示例模式:接受来自任何子域的消息
window.addEventListener('message', (event) => {
    if (event.origin.endsWith('.sharepoint.com')) {
        // 处理消息 - 这可能很危险
    }
});
  • 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组件中发现了一个通过深层链接触发的一键XSS漏洞。
  • MSRC案例研究 IV:在Teams Meetings中的Power Virtual Agents中发现了一个零点击XSS(我们将在下面研究)。 在这两种情况下,根本原因都涉及设置了isFullTrusttrue的Teams应用程序,允许它们使用postMessage()与主Teams窗口通信。这些应用程序还具有过于宽泛的validDomains列表,使得能够与可能被攻破或被滥用的域进行跨窗口通信。

MSRC案例研究 IV – 通过Teams中的Power Virtual Agents实现的零点击XSS

重要的应用程序清单设置
1
2
3
4
5
6
7
8
9
// 应用清单示例片段
{
    "isFullTrust": true,
    "validDomains": [
        "*.microsoft.com",
        "*.powerapps.com"
        // ... 其他域
    ]
}

每个Teams应用程序都包含一个清单——一个定义其功能和受信任域的JSON文件。对于postMessage安全,有两个字段尤其相关:

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

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

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

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

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

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

可视化利用过程

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

1
2
3
// 示例利用代码
// 恶意负载托管在:
const exploitUrl = "https://upload.microsoft.com/teamsExploit_fullTrust_FilesandDocs_graph.js";

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

1
2
3
4
5
6
7
// 触发代码示例
// 为了触发利用,攻击者捕获Teams Meeting期间合法的App Share请求,并将contentSharing负载中的Identifier字段替换为base64编码的恶意JSON对象:
const maliciousPayload = {
    "appId": "c3c3d39f-7b77-454c-ba5c-57c21c0c8c08", // 对应Power Virtual Agent的应用ID
    // ... 其他字段
};
// 通过将请求方法从PUT更改为POST,将API路径从updateEndpointMetadata更改为addModality,对此负载进行base64编码并替换addModality请求中的identifier值,有可能获得202 Accepted响应代码。

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

实现零点击XSS

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

  • 无需任何用户交互即可执行攻击者控制的代码。
  • 跨租户工作,意味着来自另一组织的访客用户可以利用此漏洞。
  • 绕过基于UI的限制——虽然UI可能阻止访客用户发起App Share,但后端逻辑不强制执行此限制,允许利用成功。
缓解措施

一些简单但影响深远的改变可以显著减少攻击面:

  • 应用级加固
    • 收紧域限制:避免使用过于宽泛的模式,如*.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漏洞带来的风险,我们建议采取以下行动:

  • 审计应用清单:将validDomains限制在绝对必要的域。避免使用通配符,如*.powerapps.com*.sharepoint.com,它们可能无意中扩大攻击面。
  • 限制权限:仅在场景必需时在Teams应用清单中使用isFullTrust: true
  • 强制执行CSP头:在内容安全策略中使用frame-ancestorsiframe-src指令,以防止未经授权的嵌入和框架。
  • 验证来源:确保所有postMessage监听器根据严格的允许列表检查event.origin,切勿仅依赖窗口对象引用。
  • 移除未使用或悬空域:定期审查并从配置中移除任何过时的域,防止它们成为无声的后门。
  • 利用CodeQL和动态分析:使用CodeQL查询来检测不安全的postMessage模式(例如通配符targetOrigin值或缺失的来源验证),并将动态分析工具集成到构建管道中,以便及早发现漏洞。

客户指导的示例CodeQL查询

查找使用通配符来源的postMessage调用

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

1
2
3
4
// 查找targetOrigin为通配符的postMessage调用
from PostMessageCall postMessageCall
where postMessageCall.getArgument(1).getStringValue() = "*"
select postMessageCall

检测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, "This message handler does not validate the origin of incoming messages."

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

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

迈向默认安全

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

虽然微软工程团队(EG)负有消除不安全模式(如通配符targetOrigin值或过于宽松的validDomains)的主要责任,但客户也可以采取一些步骤来保护自己。在客户托管自己的应用程序或门户(例如自定义Teams应用程序、Power Apps门户或嵌入式Microsoft服务)的场景中,他们可以强制执行严格的内容安全策略(CSP)头,如frame-ancestorsiframe-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 设计