【编者的话】Dropbox 的 Web 安全防护措施之一是使用基于内容的安全策略(CSP)。Dropbox 的安全工程师 Devdatta Akhawe 通过四篇文章,介绍了 CSP 在 Dropbox 中推广的细节和经验。Dropbox 的 CSP 原则大大降低了 XSS 和内容注入攻击的风险。不过,大规模使用比较严苛的 CSP 规则将面临诸多挑战。我们希望通过这四篇 CSP 系列文章,将 Dropbox 在实践 CSP 过程中的收获分享给广大开发社区的朋友们。第一篇文章主要介绍如何在规则中设置报表筛选管线来标记错误;第二篇介绍 Dropbox 如何在上述规则中配置随机数及缓解unsafe-inline
带来的安全风险;第三篇介绍如何降低unsafe-eval
造成的风险,以及介绍 Dropbox 所开发的开源补丁;最后一篇介绍在权限分离机制下,如何减小第三方软件接入时的风险。本篇是该系列文章的第四篇,主要介绍在权限分离机制下,如何减小第三方软件接入时的风险。
在本系列之前的几篇文章中,我们讨论了 Dropbox 配置 CSP 的经验,着重介绍了控制脚本源的 script-src 指令。通过锁定脚本源白名单、配置随机数源以及限制 unsafe-eval 风险等措施,Dropbox 的 CSP 规则有效地缓解了 XSS 注入攻击。在所支持的浏览器中,只有来自于我们自己的网站、CDN 或其他受信第三方的代码可以在 Dropbox 的网页应用中运行。
然而,配置 CSP 同时使用第三方接入又有其特有的风险与挑战。在本篇文章中,我们会讨论如何用 HTML5 权限分离的方法来解决此问题。
第三方接入的风险
首先举一个例子来说明在我们网站上运行的第三方接入代码:企业版Dropbox 中有一个 SnapEngage 提供的即时聊天小工具,根据网页应用与 SnapEngage 集成的机制,我们在 HTML 页的脚本节点中插入了以下代码。
var se = document.createElement('script'); se.type = 'text/javascript'; se.async = true; se.src = '//storage.googleapis.com/code.snapengage.com/js/' + chatId + '.js'; se.onload = se.onreadystatechange = function() { ... //elided var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(se, s);
上面的代码生成了指向 SnapEngage 库的脚本节点,同时设置了 onload 处理器,并将节点插入到页面中。载入的 JavaScript 库接下来生成显示聊天工具的标记和相关的事件处理器,并把它们插入到页面中。
这一过程存在着两个关键的问题。首先,聊天工具的标记和事件处理器并未使用 CSP 规则,因此,如果聊天工具使用了 eval 或内联事件处理器,我们的 CSP 规则便会阻止启动聊天工具。当我们第一次在 Dropbox 网站上为 script-src 配置随机数时,嵌入了聊天工具的页面不得不禁用 CSP 规则,这也就增加了页面遭受 XSS 攻击的风险。
另一个问题是一个不易察觉的威胁:该过程增加了 Dropbox 的可信计算基,最终包含了SnapEngage 的服务器。根据同源策略,所有运行在www.dropbox.com 源上的代码拥有相同的权限。增加可信计算基实质上隐性地增加了安全风险。未来的网络平台提升方法可以降低这一类的安全风险,但该方法目前仍在万维网联盟(W3C)内部讨论。
我们很了解上述风险,Dropbox 第三方接入的提供者也对自身进行了全面审查,并且强制执行了安全条约。但在实际中,仍然存在某些第三方提供者并未认真执行安全检查的情况,本着对用户负责的态度,我们认为此类数据安全风险过高无法接受,必须采取相应措施降低风险。
对此,一个可行的解决方案是,只将接入代码复制到我们的服务器中,同时修改聊天工具,使之去掉内联事件处理器。然而这种侵入式的解决方法代价巨大,更为糟糕的是,只要聊天工具升级更新,Dropbox 就必须人工进行检查和提交代码。如果将这个步骤自动完成,那么同样会产生许多安全隐患。
补救方法:权限分离
我们使用权限分离的方法来解决第三方接入所带来的问题,其核心思想很简单:在无权限的源(origin)中运行第三方代码,避免了直接在Dropbox 源中运行第三方代码。第三方代码通过iframe 接入网站,iframe 间的通信使用postMessage 函数。Dropbox 源提供更小的受信API,在不降低安全性的前提下保证了相关功能。
这同 OpenSSH 和 Google Chrome 的权限分离架构很类似,无权限的子进程与无权限的源通信,有权限的进程与有权限的(Dropbox)源通信。不过与库应用中使用的 IPC 机制不同,我们使用 postMessage 实现 iframe 间的通信。
具体来看一下 SnapEngage 接入到底怎样做到了权限分离。当载入一个含有 SnapEngage 聊天工具的网页时, https://www.dropbox.com 源中的代码生成一个指向 https://www.dbxsnapengage.com 的 iframe。接下来,dbxsnapengage.com 上的代码载入 SnapEngage 聊天工具。iframe 自带的 CSS 隐藏聊天工具的边框,看起来聊天工具便和 Dropbox 网页天衣无缝地组合在了一起。
当然,这种方法的效率并不高。为了维护相关功能,聊天工具需要和主页集成。比方说,我们希望只在用户点击页面顶端的“Chat”按钮时,聊天工具才显示出来。之前的 JavaScript 代码通过监测“Chat”按钮上的按键动作,调用 startSupportChat 函数来实现该功能。
function startSupportChat() { SnapEngage.setWidgetId(SUPPORT_ID); SnapEngage.setUserEmail(chatData.Email, true) SnapEngage.startChat("How can we help you today?") }
startSupportChat 函数会调用初始化聊天功能的相关 SnapEngage 代码。由于 SnapEngage 代码运行在 dbxsnapengage.com 上,这个函数并没有产生任何效果,也不存在于 www.dropbox.com 当中。我们对此的做法是,在 www.dropbox.com 上修改代码来给 iframe 发送消息。
DropboxSnapEngage.startSupportChat = function() { this.chatRequested = true; DropboxSnapEngage.showSnapEngageIframe(); return DropboxSnapEngage.sendMessage({ 'message_type': 'startSupportChat', 'chatData': this.chatData }); }; DropboxSnapEngage.sendMessage = function(data) { var content_window; content_window = DropboxSnapEngage.getSnapEngageIframe().contentWindow; return content_window.postMessage(data, this.SNAPENGAGE_IFRAME_ORIGIN); };
上述代码给 dbxsnapengage.com 的 iframe 发送消息。接着,dbxsnapengage.com 通过定义下面的 postMessage 事件处理器来调用 startSupportChat 函数。
function receiveMessage(event) { if (!validOriginURL(event.origin)) return; var data = event.data switch (data.message_type) { //elided ... case "startSupportChat": startSupportChat(); break; //elided ... } }
最终的效果为,点击“Chat”按钮后将显示 iframe,并给聊天工具代码发送消息以启动聊天功能。所有的聊天工具代码都运行在无 Dropbox 权限的 dbxsnapengage.com 源中。
以上所讲的仅仅是一个例子,我们在 Dropbox 网站中不同的位置都使用了权限分离的方法,有效地降低了第三方接入(例如,支付服务提供商的接入)所带来的风险。另外,作为第三方提供者的 SnapEngage 不需要做任何的改动,可以继续使用内联事件处理器。这个设计的特点虽然看起来微不足道,但事实上非常重要。我们拥有并运营着 dbxsnapengage.com 域名,也开发完成了跨权限的 postMessage API。
查看英文原文: [CSP] Third Party Integrations and Privilege Separation
编后语
《他山之石》是 InfoQ 中文站新推出的一个专栏,精选来自国内外技术社区和个人博客上的技术文章,让更多的读者朋友受益,本栏目转载的内容都经过原作者授权。文章推荐可以发送邮件到 editors@cn.infoq.com。
感谢魏星对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群(已满),InfoQ 读者交流群(#2))。
评论