在这篇博文中,我们介绍了在 Skiff 中发现的漏洞的技术细节。我们展示了一段看似无辜的代码如何导致跨站点脚本问题,使攻击者能够窃取解密的电子邮件并冒充受害者。
作为我们的 3 篇文章系列的一部分,我们将在下周介绍在 Tutanota Desktop 中发现的另一个严重漏洞。攻击者可能利用该漏洞窃取电子邮件,甚至在受害者的计算机上执行任意代码。
Sonar Research 团队在 Skiff Web 客户端的开源代码中发现了一个跨站脚本漏洞。由于 Web 客户端是用户输入密码后进行电子邮件解密的地方,因此它也是电子邮件以其解密形式存在的地方。因此,攻击者可以窃取解密的电子邮件并冒充受害者,绕过端到端加密。
这次,攻击者必须发送两封电子邮件,受害者必须查看这两封电子邮件。第二封电子邮件包含受害者必须点击的链接。
我们负责任地于 2022 年 6 月向供应商披露了这些漏洞,并在不久后修复了这些漏洞。
在 Web 应用程序中处理用户控制的 HTML 总是会增加跨站脚本 (XSS) 的风险。虽然发件人可能希望设计其消息并包含图像,但其他 HTML 标记(例如)<script>
可能会产生不良影响并危及读者的安全。这对于常规网络邮件服务来说已经很危险了,任何人都可以通过知道用户的电子邮件地址来向用户发送恶意电子邮件。
对于端到端加密和注重隐私的网络邮件程序来说,这甚至更加危险,因为用户对服务更加信任。如果攻击者可以在此类应用程序的上下文中执行任意 JavaScript,他们就有可能窃取解密的电子邮件和私钥、取消用户的匿名性并冒充受害者。
为了避免这一切,网络邮件发送者花费了大量精力来确保恶意 HTML 无法通过。大多数使用最先进的 HTML 清理程序(例如DOMPurify)来消除恶意 HTML。这是一个很好的第一步,但即使是经过清理的数据也非常脆弱,处理数据时的细微错误可能会危及整个应用程序的安全。
以下部分将解释我们在Skiff中发现的代码漏洞。我们还将强调现代网络防御机制的重要性,它们如何让攻击者的日子变得更加艰难,以及在适当的条件下如何仍然可以绕过它们。最后,我们研究 Skiff 团队如何修复这些问题以及如何避免代码中出现此类漏洞。
准备好了解有关 mXSS、沙箱绕过和 CSP 小工具的故事!
为了确保服务的安全,Skiff 采用了多种防御措施。他们首先使用 DOMPurify 清理电子邮件正文的 HTML。之后,他们执行更多步骤,包括以下转换:
Skiff-mail-web/components/Thread/MailHTMLView/injectIDs.ts:
const injectShowPreviousContainer = (dom: Document) => { const quote = dom.querySelector('[data-injected-id=last-email-quote]'); if (quote) { const div = document.createElement('div'); div.setAttribute(INJECTED_ID_ATTR, InjectedIDs.ShowPreviousContainer); quote.parentElement?.insertBefore(div, quote); }};
此函数标记电子邮件线程中先前引用的电子邮件的开始。它在第一个具有值为 的属性的<div>
元素之前插入一个空元素。这个修改乍一看似乎很无辜,因为只插入了一个空元素。data-injected-id
last-email-quote
然而,元素的插入会导致基于突变的跨站脚本(mXSS)。让我们看看以下有效负载:
该有效负载很好地通过了清理,因为<img>
带有事件处理程序的标签隐藏在属性值中。元素的内容<style>
在此处被解析为 HTML 而不是原始文本,因为它位于元素内<svg>
,因此适用 SVG 解析规则。
之后,该injectShowPreviousContainer
函数插入<div>
标签,生成以下 HTML 树:
如果我们查阅HTML 规范,我们可以看到<div>
元素不是<svg>
元素的有效子元素。由于这是对 DOM 的显式修改,因此不会引发错误,并且元素保留在插入的位置。
在电子邮件处理代码的稍后阶段,经过清理的 DOM 树通过读取其innerHTML
属性再次序列化回其字符串表示形式:
Skiff-mail-web/components/Thread/MailHTMLView/injectIDs.ts:
export const injectIDs = (html) => { const dom = document.implementation.createHTMLDocument(); // ... injectShowPreviousContainer(dom); // ... return dom.body.innerHTML;};
innerHTML
为了最终渲染处理后的电子邮件,通过 React 的dangerouslySetInnerHTML
属性将生成的 HTML 分配给元素的属性,从而再次解析生成的 HTML :
Skiff-mail-web/components/Thread/MailHTMLView/MailHTMLView.tsx:
const MailHTMLView: FC<MailViewProps> = ({ email }) => { // ... return ( // ... <div className='ProseMirror' dangerouslySetInnerHTML={{ __html: purifiedContent }} ref={setEmailDivRef} style={{ fontFamily: "'Skiff Sans Text'" }} /> </div>, // ... );};
在重新解析期间,浏览器将尝试纠正 HTML 中的任何错误。一般来说,HTML 解析器非常宽松,并试图掩盖开发人员的任何错误。他们多好啊,对吧?例如,解析器将尝试关闭缺少结束标记的元素、规范化属性分隔符等等。
对于 Skiff,输入的这种变化会导致以下 HTML 被插入到页面中:
我们可以观察到该<div>
元素被移动到<svg>
元素之外,这是合理的,因为它不是有效的子元素。
然而,该元素也与其前身一起<style>
被移动到该元素之外。<svg>
这种情况与上周的 Proton Mail 漏洞很相似!在清理期间,<style>
元素使用 SVG 规则进行解析,而在重新解析期间则使用 HTML 规则进行解析。
这种解析差异可以像以前一样被滥用,导致<img>
标签被插入到 DOM 中并onerror
在电子邮件呈现期间触发其事件处理程序。因此,利用与 Proton Mail 类似的有效负载,我们发现了一种绕过 Skiff 清理过程的方法,允许将任意 HTML 插入页面。
如前所述,有多重防御措施。消毒剂之后,下一个是 iframe 沙箱,就像我们为 Proton Mail 介绍的那样。我们可以在 Skiff 中找到相同的指令,但对于 Safari 没有特殊情况:
allow-same-origin
allow-popups
allow-popups-to-escape-sandbox
这意味着逃离沙箱的唯一方法是在新选项卡中打开有效负载,这又要求受害者单击链接。
攻击者可以使用 CSS 泄漏技术来获取受害者可以单击的同源链接。Skiff 使用 blob URL 来呈现电子邮件中的内联图像。这使得攻击者可以通过将 URL 作为附件发送来创建具有任意内容和类型的 URL。Blob URL 继承创建它们的页面的来源,因此它们将能够访问原始页面的数据。
然后,攻击者可以在电子邮件中包含 CSS 有效负载以及附件,从而将 Blob URL 泄漏回给他们。然后,他们将使用它发送一封后续电子邮件,其中包含指向该 blob URL 的链接。通过target="_blank"
对该链接进行设置,该 URL 将始终在新选项卡中打开。
查看上周的博客文章,了解 Blob URL 创建和 CSS 泄漏技术的详细信息!
最后一道防线是 Skiff 的内容安全策略 (CSP)。在这里,我们有很多指令,最有趣的指令如下:
default-src 'self'
img-src https://*
style-src 'unsafe-inline'
script-src 'unsafe-eval' http://hcaptcha.com
前三个与 Proton Mail 类似,允许在电子邮件中使用远程图像和内联样式。最后一条指令又很有趣:它允许来自 hCaptcha(一种验证码服务)的脚本,并且允许脚本使用该eval()
函数。
攻击者可以使用已知的小工具绕过该指令。我们观察到它hcaptcha.com
托管在流行的内容交付网络和 DDoS 保护提供商 Cloudflare 后面。这意味着hcaptcha.com
将在路径下提供一些实用程序脚本/cdn-cgi/scripts/
。其中一些脚本包含允许在允许的情况下绕过站点 CSP 的小工具unsafe-eval
。这项技术是Pepe Vila 在 2020 年发现的,它非常适合我们的场景。有关此方法的详细信息,请查看Pepe 的页面!
至此,漏洞利用策略就完成了。攻击者首先发送一封带有附件的电子邮件,该附件会导致创建 blob URL。该电子邮件还包含 CSS,该 CSS 通过前面描述的方法将此 URL 泄露给攻击者服务器。一旦知道 blob URL,攻击者就会发送一封后续电子邮件,这次包含受害者必须单击的链接。发生这种情况时,blob URL 将在新选项卡中打开,其中 hCaptcha/Cloudflare 脚本小工具绕过 CSP 并在 Skiff Web 应用程序的上下文中执行任意 JavaScript。
由于我们发现的代码漏洞造成了严重影响,因此让我们了解如何修复该漏洞以及如何避免代码中出现类似问题。
Skiff 团队采用了一种可以应用于所有类似情况的通用方法。他们在完成所有修改后移动了清理程序,以确保最终的 HTML 是安全的:
const bodyContent = getEmailBody(email);const dom = new DOMParser().parseFromString(bodyContent, 'text/html');proxyAttributes(dom, disableRemoteContent);rewriteCSSAttribute(dom, originUrl, disableRemoteContent);const sanitizedContent = DOMPurify.sanitize(dom.documentElement.outerHTML);return getIframeHtml(sanitizedContent, extraStyle);
为了避免此类消毒剂绕过,我们有一些建议:
如果可能,请在客户端而不是服务器上进行清理。HTML 解析器是复杂的野兽;使用两个不同的就像要求解析器差异一样。
使用最先进的消毒剂。这可以是DOMPurify,也可以是未来将内置到浏览器中的即将推出的Sanitizer API 。如果您使用晦涩或过时的消毒剂,它们可能会错过奇怪的怪癖,让您容易受到伤害。
清理数据后切勿修改数据。这并非特定于 HTML,而是特定于任何需要清理的数据。数据结构越复杂,清理后修改它就越危险。
如果可能的话,在清理 HTML 后甚至不要重新解析它。DOMPurify 可以配置为返回清理后的 DOM 树而不是字符串。如果直接将此树插入到页面的 DOM 中,浏览器将不会改变其内容,从而减少 mXSS 的机会。
Copyright © 2022 All Rights Reserved. 地址:上海市浦东新区崮山路538号808 苏ICP123456 XML地图