突破限制-一份安全编写和审计Chrome扩展程序的指南(上)

浅析Chrome扩展程序安全的优先级

Chrome的扩展安全性和用于审计Chrome扩展漏洞的方法似乎是一项令人震惊的现有技术话题。特别是与其他平台相比,如Electron,它已对该话题开展了进一步地研究。在互联网上搜索到的用来审核Chrome扩展程序的指南和工具的数量少之又少,只有一篇用来描述Chrome扩展程序安全模型的学术论文和2013年的一篇博客文章:在易受攻击的扩展程序中展示XSS漏洞。其他搜索结果似乎已经过时了,例如Chrome扩展指纹识别指南,这个指南已不再适用于新的Chrome扩展程序。

当然,并不是说Chrome扩展程序中的安全问题没有被发现或者说特别罕见。比如比较具有代表性的例子——Reddit增强套件(RES)扩展程序中的跨站点脚本(XSS)漏洞。有关这个漏洞的详细摘要,请参阅文章, 而且目前该扩展程序目前已有150万用户。

这个例子并不算是最糟糕的情况,因为这个XSS漏洞是在扩展的Content Script(内容脚本)中而不是在Background Page(后台页面)中(本指南将深入讨论其间差异)。简而言之:后台页面(可访问所有特权扩展API的页面)中的漏洞比任何常规XSS漏洞都要糟糕得多。它造成了让攻击者有滥用扩展声明的任何API的能力,可以冒充成受害者访问所有网站,修改/编辑浏览器的书签,历史记录等信息的能力。例如,Steam Inventory Helper扩展程序存在在后台页面的内容中执行任意的JavaScript的漏洞,从而让它们可以在已经认证了它们的网站上劫持受害者的所有账户信息。

鉴于Chrome浏览器及其扩展有着令人难以置信的受欢迎程度,因此仔细研究这个平台可能发生的安全隐患很有价值的。这篇指南尝试概述扩展程序的安全反模式,并为开发人员和安全研究人员在审核Chrome扩展程序时提供可靠的服务(tarnish)。

在深入了解Chrome扩展程序中的安全反模式之前,首先要先了解下这些扩展程序的结构。直截了当地说:Chrome的开发人员已经把很多注意力放在了扩展安全和不安全的反模式上。他们的架构非常清晰,正如我将在下面讨论的,其中很多都是以构建开发人员无法轻松攻破自己的环境为核心思想设计的。在这个我们拥有ElectronNW.js等平台的时代,它似乎有意将跨站点脚本(XSS)的系统性问题带到桌面,并将其全部转换为远程执行代码执行(RCE)而没有采取任何保护措施;Chrome的扩展环境是另一个不稳定因素。即使Chrome扩展程序无法执行任意一个系统命令,但它们仍然非常谨慎,以确保开发人员想出错都是很特别困难的。

独立而活跃的领域

免责声明

本节将详细介绍Chrome扩展程序的运行方式。如果您已经熟悉这一点,那么您可以直接跳转到“偷窥扩展程序中未被污染的安全反模式”部分。哪怕您曾经开发过Chrome的扩展程序,但阅读这部分内容也可以当作是复习。

拓展程序基本布局-home中的manifest.json

Chrome扩展程序的文件结构实际上非常简单。Chrome扩展本质上是一个文件扩展名为.crx的压缩文件夹,扩展的核心是文件夹根目录下的manifest.json文件,该文件指定布局、权限和其他配置选项。而且理解manifest.json格式对于审核安全漏洞的扩展是至关重要的。扩展的所有路径都是相对于manifest.json所在的基础位置。因此,如果您在根目录中有一个名为example.jpg的图像,它将位于chrome-extension://[EXTENSION_ID]/example.jpg (extension ID为Chrome扩展私钥的base32编码的SHA256哈希值)。

扩展体系结构,命名空间隔离和DOM

Chrome扩展程序的工作方式在怎么利用它们的方面存在很大差异,其中大部分实际上在我之前的学术论文链接中有简述,但我会在这里深入研究它,因为论文已经有点过时了。

可视化的Chrome扩展程序布局一目了然:

image.png

上图显示了Chrome扩展程序的不同部分。每个彩色框都是一个单独的JavaScript变量命名空间。单独的变量命名空间意味着如果您在JavaScript中声明了一个变量,如下所示:

1
var test = 2 ;

这个变量只能在自己的命名空间中访问(不同颜色的框不能直接访问彼此的变量)。例如,如果这是后台页面中的变量,则无法从内容脚本或网页中访问它。对于内容脚本声明的变量也是如此,后台页面或网页无法访问它们。这种沙箱可防止恶意网页干扰正在运行的内容脚本或扩展程序的后台页面,因为它无法访问或更改任何变量或函数。

Chrome扩展中的同源策略(SOP)

如果你了解同源策略在Chrome扩展中是如何运用的,那么这种分离的意义就非常容易理解了。每个Chrome扩展程序都有自己的来源,格式如下:

1
chrome-extension://[32_CHAR_EXTENSION_ID]

这意味着Chrome扩展程序API可以访问属于此来源的任何资源。这种原始结构很有意义,因为所有Chrome扩展程序的资源都位于chrome-extension://[32_CHAR_EXTENSION_ID]/ 目录中。 当我们讨论后台页面和浏览器操作页面时,所有的这些都在chrome-extension://[32_CHAR_EXTENSION_ID] 源目录中执行。示例如下:

1
chrome-extension://[32_CHAR_EXTENSION_ID]/index.html

1
chrome-extension://[32_CHAR_EXTENSION_ID]/example.html

这两个页面都可以访问彼此的DOM和JavaScript命名空间,因为它们具有相同的来源。请注意,这意味着在通过iframe contentWindowwindow.opener进行访问时,每个后台页面的变量命名空间不会以任何全局方式彼此之间进行共享(除了多个后台页面脚本在运行时被整合到一个后台页面的情况)。您可以通过在Chrome中启用开发者模式来查看和调试后台页面。

内容脚本的工作方式略有不同,它们在其作用范围内操作网页的来源。因此,如果您在https://example.com 上运行内容脚本,则其有效来源就为https://example.com 。这意味着它可以执行一些操作,诸如和https://example.com 的DOM进行交互,添加事件侦听器以及执行XMLHTTPRequest以检索此来源的网页等。但是,它不能修改相应扩展的后台页面的DOM,因为它们的来源是不一样的。话虽如此,内容脚本确实有更多的权限,可以向后台页面发送消息,并调用一些有条件限制的Chrome扩展API。这是很怪异的设置,因为感觉就像是即使它们仍然共享一个DOM,但你的内容脚本和你的网页也会由于命名空间隔离而在单独的“pages”中运行。如果想要在Chrome中查看内容脚本并对其进行调试的话,您可以通过点击 选项>更多工具>开发者工具,然后弹出Chrome开发者工具菜单,显示开发人员工具后,单击“源”选项卡,然后单击子选项卡“内容脚本”。在这里,您可以看到各种扩展运行的内容脚本,并可以设置断点来监视执行的流程:

image.png

我审核Chrome扩展程序所花的时间大部分都在上面的Chrome开发人员面板上,即设置断点并执行。

通过注入和消息传递来跨越障碍

然而,尽管命名空间分离,但Chrome扩展程序仍有足够的空间来完成自己的工作。例如,假设内容脚本需要检索网页命名空间中定义的变量的值,虽然它无法直接访问网页的命名空间,但它可以访问网页的DOM并向其中注入新的脚本标签(<script>)。然后,这个注入的脚本标签将在网页的命名空间中执行,并且可以访问它的变量。获取变量后,注入的脚本可以通过postMessage()函数将值传递回内容脚本。这是通过在父“网页的DOM”框中包含内容脚本和网页来显示的,它们都可以访问网页的DOM但是无法访问彼此的名称空间。下图演示了从网页中抓取变量并将其传递回内容脚本的流程:

image.png

在Chrome扩展审计过程中,重要的事情之一就是了解这些独立的领域之间是如何协同工作的。答案是(主要)通过消息传递。在Chrome扩展程序中,有几种不同的方式可以传递消息,例如chrome.runtime.sendMessage()用于将消息从内容脚本发送到后台页面,或者window.addEventListener()用于将消息从网页传递到内容脚本。从安全角度来看,每当消息从较低特权空间中传递到较高特权空间时(例如,从内容脚本到后台页面的消息),通过非预期的输入获取可能的利用途径。因此消息传递的范围必须正确合理,以确保敏感数据不会被发送到非预期的来源。对于postMessage(),就不能使用通配符“*”来发送消息,因为任何带有引用窗口的网页都可能监听它们。

Web可访问资源和导航阻塞

似乎命名空间隔离还不够,Chrome扩展资源还加了另外一层保护。默认情况下,Chrome扩展程序中的文件不能通过iframed、sourced或者是其他的方式包含网络上的常规网页。因此,https://example.com 可能无法使用chrome-extension://pflahkdjlekaeehbenhpkpipgkbbdbbo/test.html 。然而,Chrome的扩展API却允许你通过使用扩展清单中的web_accessible_resources指令来声明这些资源,进而放宽这一限制条件。如果您希望常规网页可以包含扩展程序中的JavaScript,图像或者其他的资源,那么这个功能将非常重要。从安全角度来看,这个问题的缺点在于,任何设置了该标志的HTML页面都可以通过clickjacking(点击劫持),将恶意的输入传递到location.hash,通过postMessage()传递非预期的消息,或者通过非预期的顺序来嵌入和运行后台页面。所以,开发人员使用这个指令对大量资源进行通配是很危险的。此外,如果Chrome扩展程序通过这个指令公开所有的内容,Internet上的所有网页都可以使用这些可溯源的资源来指纹识别用户运行的特定扩展程序,这在开发和一般的网络跟踪中都有很多的用途。

后台页面和内容安全策略

后台页面在各种领域中具有最高权限,因为它可以调用扩展程序清单中声明的所有Chrome扩展API。这些API是扩展程序功能强大的核心,能够进行管理cookie,书签,历史记录,下载,代理设置等操作。鉴于这些页面的强大性质,Chrome要求开发人员以特定的最小要求声明内容安全策略(CSP)。默认情况下的Chrome扩展程序文档声明的策略如下:

1
script-src 'self'; object-src 'self'

这是正确的,但值得一提的是,默认策略实际上是(您可以使用Chrome开发人员工具的网络面板自行验证):

1
script-src 'self' blob: filesystem: chrome-extension-resource:; object-src 'self' blob: filesystem:;

上述的策略略微宽松,以允许一些常规的JavaScript操作,例如创建blob: URIs、和文件系统交互。当然,开发人员经常会看到这个默认策略,并对它的严格程度感到很反感。这种反感经常会促使开发人员尝试尽可能地放宽CSP以便“使其工作”。Chrome团队预料到了这种危险,并附加了额外的要求,以防止开发人员让他们的CSP变得过于宽松。因此,无法在Chrome扩展程序CSP中使用‘unsafe-inline(不安全内联)’的源代码( 保留带有nonce的script )。这意味着开发人员永远不会使用类似于以下的内联 JavaScript 执行:

1
2
3
4
5
Name: <input onfocus=”example()” name=”test” />

<a href=”javascript:example()”>Click to start</a>

<script>alert(“Welcome!”)</script>

虽然这对于已经习惯了这种Web开发风格的开发人员来说很痛苦,但就其安全优势而言,作用不可估量。在这个过程中,Chrome使开发人员在其后台页面中编写跨站点脚本(XSS)漏洞变得更加困难。据我审核了数不胜数的Chrome扩展程序的经验来看,这是一个完全可利用的漏洞中唯一存在的缓解因素。此外,值得一提的是,这个要求通常会迫使开发人员用更简洁明了的方式来编写扩展程序,因为他们必须将视图和核心逻辑分开。

但是,您仍然会犯很多您在CSP中看到的常见错误。开发人员会(并且经常)将‘unsalf-eval’添加 到他们的CSP,并经常使用通配符CDN,并且任何人都可以上传其他来源的源脚本。这一系列操作通常让攻击者绕过CSP的所有保护措施有机可乘。


本文翻译自: https://thehackerblog.com/kicking-the-rims-a-guide-for-securely-writing-and-auditing-chrome-extensions/


文章作者: Ginove
文章链接: https://ginove.github.io/2018/09/09/突破限制-一份安全编写和审计Chrome扩展程序的指南-上/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ginove