本文基于Tobias Lauinger、Abdelberi Chaabane、Sajjad Arshad、William Robertson、Christo Wilson和Engin Kirda的原创研究,该研究首次发表于2017年网络和分布式系统安全研讨会论文集(汝不可依赖于我:分析网络上过时的JavaScript库的使用情况; https://seclab.ccs.neu.edu/static/publications/ndss2017jslibver.pdf)。
许多网站使用第三方组件,例如JavaScript库,这些库捆绑了有用的功能,使开发人员可以避免重复造轮子。 jQuery (https://jqueryjs.cn/) 可以说是目前最流行的开源JavaScript库——在亚马逊Alexa (https://www.alexa.com/topsites) 确定的最受欢迎的网站中,有 84% 使用了 jQuery。但是,当库出现安全问题时会发生什么?使用这些库的网站很可能会继承这些问题,并容易受到攻击。
考虑到使用已知漏洞库的风险,重要的是要知道这种情况在实践中发生的频率,更重要的是,应该责怪谁包含易受攻击的库——是网站的开发人员,还是网站上加载的第三方广告或跟踪代码?
我们着手回答这些问题,发现 37% 的网站至少使用了一个已知的易受攻击的库,并且库的包含方式常常出乎意料,这表明在Web上处理库方面显然有改进的空间。为此,本文就如何改善这种情况提出了一些建议。
在深入探讨如何在Web上检测易受攻击的库的使用之前,我们需要就什么构成漏洞达成一致。首先,我们只对将在客户端(即Web浏览器中)运行的代码感兴趣。JavaScript是用于此目的的事实标准语言,并且因安全漏洞(如XSS(跨站点脚本))而臭名昭著,XSS允许攻击者将恶意代码(或HTML)注入网站。特别是,如果JavaScript库接受来自用户的输入,但对其验证工作做得不好,则可能会潜入XSS漏洞,并且所有使用此库的网站都可能变得容易受到攻击。
例如,考虑jQuery的$()
函数。它根据传递的参数类型具有不同的行为:如果参数是包含CSS(层叠样式表)选择器的字符串,则该函数将在DOM(文档对象模型)树中搜索相应的元素,并返回对它们的引用;如果输入字符串包含HTML,则该函数创建相应的元素并返回引用。因此,将未经正确消毒的输入传递给此函数的开发人员可能会无意中允许攻击者将代码注入页面,即使程序员的意图是选择现有元素。虽然此API设计将便利性置于安全考虑之上,并且文档中可以更好地突出显示其含义,但这并不自动构成库中的漏洞。
但是,在旧版本的jQuery中,$()
函数在解析字符串参数时的宽松性可能会导致复杂化,从而误导开发人员认为,例如,任何以#
开头的字符串都将被解释为选择器,并且可以安全地传递给该函数,因为#test
选择具有标识符test
的元素。然而,jQuery将任何位置包含HTML <tag>
的参数都视为HTML (https://bugs.jquery.com/ticket/9521),因此诸如#<img src=/ onerror=alert(1)>
之类的参数将导致代码执行,而不是选择。此行为被认为是漏洞并已修复。
JavaScript库中的其他漏洞包括库未能清理预期为纯文本但内部传递给eval()
或document.write()
的输入的情况,这可能导致它们被作为脚本执行或呈现为标记。攻击者可能会利用这些功能从用户的浏览会话中窃取数据,代表用户发起交易或在网站上放置虚假内容。因此,重要的是JavaScript库不要在其使用的网站中引入任何新的攻击媒介。
在我们的研究时,没有一个“权威”的JavaScript漏洞公共数据库。我们手动搜索了OSVDB(开源漏洞数据库)、NVD(国家漏洞数据库)、公共错误跟踪器、GitHub评论、博客文章以及Retire.js (https://retirejs.github.io/retire.js/) 检测到的漏洞列表,以收集图1中显示的11个常用库的易受攻击和已修复版本的元数据。因此,给定这11个库之一的名称和特定发布版本,我们可以说我们是否知道任何公开披露的漏洞——但可能还有更多我们不知道的漏洞。因此,我们在此报告的内容应被视为下限。
手动收集漏洞元数据是可行的,因为我们将自己限制在11个最流行的库中。但是,对于检测网站上使用的库,需要一种自动化方法。乍一看,在网站上检测库听起来并不太复杂:检查库文件在官方发行版中的调用方式,例如jquery-3.2.1.js
,并在网站加载的URL中查找该名称。不幸的是,很少有这么容易的情况。Web开发人员可以重命名文件,而且他们确实这样做了。例如,使用这种简单的策略而不是更复杂的检测方法,将错过包含Modernizr库的所有URL中的44%。这是不可接受的。
我们的方法结合了静态和动态方法。静态方法是对基于名称的方法的略微改进:我们不是通过库文件的名称来检测它们,而是通过文件哈希来检测它们。这需要一个全面的库文件哈希目录,该目录从库网站以及Google、Microsoft和Yandex维护的JavaScript CDN(内容分发网络)以及社区驱动的CDN jsDelivr、cdnjs和OSS CDN上找到的下载链接编译而来。一些库(如Bootstrap和jQuery)维护了自己的品牌CDN,这些CDN也包含在内。下载了每个库的所有版本和变体。变体通常包括带有注释的源代码“调试”版本,以及删除了空格并缩短了内部标识符的“最小化”生产版本,以实现更小的文件大小和更快的页面加载时间。
通过哈希检测库的一个缺点是,当目录中没有相应的参考文件时,无法检测到它。例如,当Web开发人员修改文件的源代码时,可能会发生这种情况。源代码修改(如添加或删除注释或自定义最小化)在实践中非常频繁地发生。在我们爬网中遇到的已知包含jQuery的脚本的随机样本中,只有15%可以通过文件哈希进行检测。因此,我们用动态检测方法补充了静态检测。
动态检测检查库在Web浏览器中加载时的运行时环境。许多库注册为窗口全局变量,并提供一个包含库版本号的属性。例如,在使用jQuery的网站上,在浏览器的开发人员控制台中键入$.fn.jquery
会返回版本号(如3.2.1
)。仅计算返回标准三组件major.minor.patch
版本号(如语义版本控制 (http://semver.org/) 中使用的版本号)的检测。按照惯例,主要版本组件针对重大更改而增加,次要组件针对新功能而增加,补丁组件针对向后兼容的错误修复而增加。丢弃具有无效或空版本属性的检测会减少误报检测的数量——即实际上与库的使用不对应的检测。
此外,为了我们的数据分析,需要每个检测到的库实例的版本号,以查找是否已知任何漏洞。不幸的是,某些库不以编程方式导出版本属性,某些库仅在较新版本中添加了此功能,并且某些库加载技术(如Browserify或Webpack)可能会阻止库注册其窗口全局变量。此外,由于任何时候只能存在一个窗口全局变量的实例,因此当在同一页面中多次加载库时,运行时仅最后一个实例可见。所有这些情况都会导致误报检测——即,即使动态检测签名在网站中存在,也无法检测到该库。
结合静态和动态检测方法克服了它们各自的局限性。我们的研究论文还描述了动态检测的离线变体,用于重复库包含的极端情况。
我们研究的一个重要方面是找出应该责怪谁包含易受攻击的库。为此,我们需要在网站中建模因果资源包含关系,以便表示库是如何包含在页面中的。例如,库可以直接在网页中引用,也可以在另一个引用的脚本加载其他资源时传递性地包含。我们将此模型称为因果关系树。
当且仅当元素A
导致元素B
加载时,因果关系树才包含有向边A → B
。本研究建模的元素是脚本和嵌入式HTML文档。每当一个元素创建另一个元素或更改现有元素的URL时,就会存在关系。示例包括创建iframe的脚本,以及更改iframe的URL的脚本。
虽然因果关系树中的节点对应于网站DOM中的节点,但它们的结构与分层DOM树完全无关。相反,因果关系树中的节点是DOM树中元素在特定时间点的快照,如果DOM元素被重复修改,则可能会多次出现。例如,如果脚本创建了一个URL为U1的iframe,稍后将URL更改为U2,则因果关系树中的相应脚本节点将有两个文档节点作为其子节点,分别对应于URL U1和U2,但引用相同的HTML <iframe>
元素。同样,因果关系树中节点的前身不一定是DOM树中相应HTML元素的前身;它们甚至可能位于两个不同的HTML文档中,例如当脚本将元素附加到不同框架中的文档时。
图2显示了因果关系树的合成示例。大的白色圆圈是文档根(主文档),实心圆圈是脚本,正方形是HTML文档(例如,嵌入在框架中)。边表示“由...创建”关系;例如,在图2中,主文档包含灰色脚本,灰色脚本又包含蓝色脚本。节点周围的虚线表示内联脚本,而实线表示从URL包含的脚本。粗轮廓表示资源是从已知的广告网络、跟踪器或社交小部件包含的。
图2中节点的颜色表示它们在DOM中附加到哪个文档:灰色对应于附加到主文档的资源,而四个颜色之一分配给框架中的每个其他文档。文档正方形包含其父位置在DOM中的颜色,以及它们自己分配的颜色。由一个框架中的脚本创建的资源可以附加到另一个框架中的文档,如图2中具有蓝色子节点的灰色脚本所示(即,蓝色脚本是DOM中蓝色文档的子节点)。
图3a显示了LinkedIn小部件,因为它包含在mercantil.com
的因果关系树中。(交互式版本在线提供,网址为https://seclab.ccs.neu.edu/static/projects/javascript-libraries/。)请注意,Web开发人员将社交网络提供的代码嵌入到主文档中,而主文档又初始化了小部件并在多个框架中创建了多个脚本。
因果关系树是使用Chromium Web浏览器的instrumented版本生成的。其Chrome DevTools协议 (https://chromedevtools.github.io/devtools-protocol/) 允许检测大多数资源包含关系;对于某些极端情况,我们不得不求助于浏览器中的源代码修改。我们还将库检测链接到因果关系树中的节点,并运行AdBlock Plus的修改版本,以标记(但不阻止)因果关系树中的广告、跟踪和社交媒体节点。在访问页面时,爬虫向下滚动以触发任何动态内容的加载。由于页面加载事件被证明不可靠,因此我们的爬虫在每个页面上停留固定延迟60秒,然后清除其整个状态、重新启动、然后继续访问下一个站点。
为了获得对Web上JavaScript库使用情况的代表性看法,我们收集了两个不同的数据集。首先,我们爬取了Alexa排名前75,000的域名,这些域名代表受欢迎的网站。其次,我们从.com
区域的快照中随机抽取了75,000个域名——即,对所有具有.com
地址的网站进行随机抽样,预计这些网站将以不太受欢迎的网站为主。这两次爬取于2016年5月进行,成功地为Alexa中的71,217个域名和.COM中的62,086个域名的主页生成了因果关系树。失败的原因是超时和无法解析的域名,这对于.COM尤其如此,因为区域文件包含可能没有活动网站的域名。
总的来说,我们的研究使用了72个开源库的静态和动态签名。我们发现Alexa网站的87%和.COM网站的65%的主页上至少有一个库。图4显示了Alexa中最常见的12个库。jQuery是迄今为止最流行的库,Alexa网站的84%和.COM网站的61%使用了jQuery。换句话说,几乎每个使用库的网站都在使用jQuery。SWFObject是一个用于包含Adobe Flash内容的库,尽管自2013年以来已停止使用,但在排名中仍位居第七(4%)和第十(2%)。另一方面,D3、Dojo和Leaflet等几个相对知名的库在两次爬网中都排在30名之后,这可能是因为它们在网站主页上不太常用。
虽然Alexa中使用的库大多数托管在与网站相同的域中,但在.COM中,大多数包含是从外部域加载的。以jQuery为例,Alexa网站中59%的包含是内部的,39%是外部的。其余的是内联包含,其中库的源代码不是从文件加载的,而是直接包装在<script> // library code here </script>
标记中。.COM爬网中只有30%的网站在内部托管jQuery,而68%的网站依赖外部托管。这突出了较大和较小网站包含库的方式的差异。
在两次爬网中,JavaScript CDN都是从中加载库的最受欢迎的域之一。在Alexa中,几乎18%的库文件是从ajax.googleapis.com
(Google的JavaScript CDN)加载的(.COM中为13%),其次是jQuery的品牌CDN code.jquery.com
(Alexa中为4%,.COM中为3%)。然而,.COM爬网中不太受欢迎的站点也经常从与域名停放和托管提供商相关的域名加载库。
当查看为什么包含库时,结果表明,Alexa中约3%的jQuery包含和.COM中几乎26%的jQuery包含是由广告、跟踪或社交媒体小部件代码引起的。对于SWFObject,Alexa中超过42%的包含来自广告。换句话说,包含现在不受支持的库的责任并不直接归咎于这些网站,而是归咎于它们正在使用的广告网络。广告、跟踪或社交媒体小部件代码通常由外部服务提供,并由网站开发人员按原样加载——他们可能不知道包含的代码会加载其他库,并且对将加载哪些版本的库没有发言权。总的来说,在Alexa的7%的站点和.COM的16%的站点上可以找到由广告加载的库。
我们编译了关于图1中显示的11个库的易受攻击版本的元数据。在Alexa网站中,38%的网站使用至少一个已知易受攻击版本的这11个库中的一个,10%的网站使用两个或多个不同的已知易受攻击版本。在.COM中,漏洞率略低——37%的站点至少有一个已知的易受攻击库,4%的站点有两个或多个——但.COM中的站点总体而言库使用率也较低。因此,那些确实使用库的.COM站点比Alexa中的站点具有更高的漏洞概率。
查看单个库表明,已知的易受攻击版本可能占这些库在实际应用中所有用途的大多数。例如,jQuery在Alexa中约有37%的已知易受攻击包含,在.COM中为55%。Angular在两次爬网中都有39-40%的易受攻击包含,Handlebars有87-88%。然而,这并不意味着Handlebars比jQuery“更易受攻击”;这仅意味着Web开发人员在使用Handlebars时比使用jQuery时更频繁地使用已知的易受攻击版本。这里的重点是已知易受攻击,因为每个库都可能包含未知的漏洞。从这个意义上讲,这些结果是易受攻击库使用量的下限。
到目前为止,我们已经检查了站点是否可能易受攻击——即它们是否包含一个或多个已知的易受攻击库——以及这如何在每个库级别上累加。现在让我们回到我们对站点如何包含库的分析。图5显示了与更高比例的易受攻击包含相关的两个突出因素
• jQuery的内联包含比内部或外部托管的副本具有明显更高的易受攻击版本比例。
• 由广告、小部件或跟踪器代码包含的库似乎比不相关的包含更易受攻击。虽然Alexa中jQuery的差异相对较小,但与.COM中广告、小部件或跟踪器代码相关的jQuery的漏洞率(89%)几乎是不相关包含率的两倍。这可能是由于在.COM中较小的站点上使用了信誉较低的广告网络或小部件,而不是Alexa中较大的站点。
此时,有必要说明一下我们研究的局限性。我们不检查在特定网站上使用库时是否可以利用库中的已知漏洞。如果Web开发人员可以确保在其站点上无法利用库漏洞,则他们无需更新到较新版本。然而,正如稍后将讨论的那样,库的发行说明很少包含足够的信息,以使非专家能够决定在特定站点上继续使用易受攻击的库是否安全。因此,在实践中,安全的做法始终是在发现库中的漏洞时进行更新。
不幸的是,由于库维护者的发布周期和修补行为,更新库依赖项说起来容易做起来难。只有极小一部分使用易受攻击库的站点(Alexa中不到3%,.COM中不到2%)可以通过仅应用补丁级更新来消除漏洞。对最不重要的版本组件的更新(例如从1.2.3
到1.2.4
)通常应被认为是向后兼容的。然而,在大多数情况下,补丁更新不可用。绝大多数站点将需要安装至少一个具有较新主要版本或次要版本的库才能消除所有漏洞。由于API中的不兼容性,迁移到这些较新版本可能需要额外的代码更改和站点测试。
除了漏洞之外,并考虑所有72个受支持的库,Alexa网站的61%和.COM网站的46%在其包含的库之一上至少落后一个补丁版本。即使这样的更新应该是“无痛的”,但它们经常被忽略。同样,Alexa网站的平均版本使用的库版本比该库的最新可用版本发布早1,177天(.COM为1,476天)。这些结果表明,大多数Web开发人员都在使用很久以前发布的库版本。以年为单位的时间差表明,Web开发人员在部署站点后很少更新其库依赖项。
分析网站上JavaScript库的使用情况表明,库通常以意想不到的方式使用。例如,在Alexa中包含jQuery的网站中,约有21%的网站在单个网页中执行两次或多次。仅此一项不足为虑;当网站包含从不同来源加载文档的<iframe>
时,由于同源策略限制脚本跨来源的访问,甚至可能有必要多次包含该库。然而,仔细观察发现,在Alexa中使用jQuery的网站中,有4%的网站在同一文档中两次或多次包含同一版本的库(.COM中为5%),而11%(6%)的网站在同一文档中包含两个或多个不同版本的jQuery。在同一文档中多次包含库不会带来任何好处,因为jQuery将自身注册为窗口全局变量。除非采取特殊步骤,否则客户端代码只能使用每个文档中最后加载和执行的实例;其他实例将被隐藏。异步包含的实例甚至可能产生竞争条件,从而难以预测最终哪个版本将占上风。
为了说明这一点,请考虑图3b中mercantil.com
的因果关系树的详细信息。该站点包含jQuery四次。所有这些包含都直接在主页的源代码中引用,其中一些彼此直接相邻。在其他站点上,重复包含是由多个脚本传递性地包含它们自己的jQuery副本引起的。虽然我们只能推测为什么会发生这些情况,但至少其中一些可能与服务器端模板化或将独立开发的组件组合到单个文档中有关。实际上,我们已经观察到这样的情况:一个捆绑了自己版本的库的Web应用程序(例如,WordPress插件)被集成到一个已经包含同一库的单独副本的页面中。由于库的重复包含不一定会破坏任何功能,因此许多Web开发人员可能没有意识到他们多次包含库,甚至更少人可能意识到重复包含可能存在潜在漏洞。
我们的研究表明,易受攻击的库在Web上被广泛使用。许多因素在起作用,并且不能让任何一个参与者对这种情况负责。相反,让我们从三个不同的角度来看待它。
网站开发人员需要了解他们正在使用的库。当库被手动复制到代码库中时,很容易忘记它。相反,我们建议在中心位置显式声明项目的依赖项。对于客户端JavaScript,Bower (https://bower.io/) 是最早的依赖管理工具之一。Yarn (https://yarn.npmjs.net.cn/) 是一个较新的工具,由NPM(Node Package Manager;https://npmjs.net.cn/)的存储库支持,其中不仅包含服务器端Node.js包,还包含客户端JavaScript库)。显式依赖项使自动将声明版本的库代码包含到项目中变得容易。此外,Retire.js (https://retirejs.github.io/retire.js/)、AuditJS (https://github.com/OSSIndex/auditjs) 或Snyk (https://snyk.io/) 等工具可以扫描声明的依赖项中已知的易受攻击版本。理想情况下,Web开发人员应使此类工具成为其构建过程的一部分,以便尝试包含已知的易受攻击库会导致构建失败。对于无法采用这种主动方法的项目,Retire.js还具有浏览器扩展,可以检测已部署网站中的易受攻击库。
库维护者采用的开发实践对库用户保持其依赖项更新的难度有很大影响。为此,我们对12个最常用的库(图4)进行了非正式调查。
在开发人员可以更新他们正在使用的库之前,必须让他们意识到需要更新。然而,这12个库中没有一个似乎维护邮件列表或其他用于安全公告的专用渠道。一些库有Twitter帐户,但这些帐户包含大量与新版本或安全问题无关的额外“噪音”。这些库似乎都没有系统地分配CVE(通用漏洞和披露)编号或在流行的漏洞数据库中注册安全问题。只有Angular在新库版本的发行说明中突出显示了已修补的漏洞;其他库通常会提及不具体的“安全修复”以及一长串其他更改(如果提及的话)。
除了难以找到有关漏洞的信息外,也很难找到有关受漏洞影响的版本范围的信息。鉴于普遍缺乏现成的信息,具有安全意识的库用户别无选择,只能在每次发布新版本时进行更新。然而,更新通常是“痛苦的”,原因有很多,从Web库开发中常见的短发布周期到API的重大更改以及每次库更新后需要进行测试。
为了以积极的态度结束本次调查,我们重点介绍了Ember (https://emberjs.com) 遵循的安全实践。其维护者承诺修补长期支持版本,以便库用户无需处理频繁的API重大更改。Ember维护安全公告邮件列表,注册CVE编号,在发行说明中提及安全问题,列出受漏洞影响的版本范围,并提供专用电子邮件地址以报告安全问题。这些做法减轻了处理漏洞的负担。让我们希望其他库维护者也会效仿。
前面的段落假设网站开发人员直接包含库,这使其有责任保持库的最新状态。然而,网络爬取的结果表明,这种假设在实践中通常不成立。实际上,许多网站开发人员加载外部脚本,例如广告、跟踪器代码或社交媒体小部件。这些第三方组件有时会自行包含库。这项研究表明,这种行为可能导致库的重复包含,并且这些间接包含会带来更高的漏洞率。在某些情况下,在iframe中沙盒化第三方代码可能是限制损害的一种选择。然而,总的来说,网站开发人员必须依靠这些组件的维护者来更新其代码。
大多数网站都使用JavaScript库,其中许多库已知存在漏洞。了解问题的范围以及包含库的许多意外方式只是改善情况的第一步。这里的目标是,本文中包含的信息将有助于为社区提供更好的工具、开发实践和教育工作。
消除准入壁垒
Rich Harris
我们必须选择构建一个每个人都可以访问的网络。
https://queue.org.cn/detail.cfm?id=2790378
JavaScript和Netflix用户界面
Alex Liu
条件依赖项解析
https://queue.org.cn/detail.cfm?id=2677720
MongoDB的JavaScript模糊测试器
Robert Guo
模糊测试器用于您的测试未捕获的那些边缘情况。
https://queue.org.cn/detail.cfm?id=3059007
Tobias Lauinger 是东北大学的博士生,对互联网规模的安全及其他方面的测量感兴趣。
Abdelberi Chaabane 是诺基亚贝尔实验室的安全研究员,其工作重点是实证性的大规模研究,以测量和理解在线威胁。
Christo Wilson 是东北大学的副教授,其工作重点是网络安全和隐私以及算法透明度。
版权 © 2018 由所有者/作者持有。出版权已授权给。
最初发表于Queue杂志第16卷第1期——
在数字图书馆中评论本文
Matt Godbolt - C++ 编译器中的优化
在向编译器提供更多信息时需要权衡:这可能会使编译速度变慢。链接时间优化等技术可以为您提供两全其美的效果。编译器中的优化不断改进,即将到来的间接调用和虚函数分派的改进可能很快会带来更快的多态性。
Ulan Degenbaev、Michael Lippautz、Hannes Payer - 作为合资企业的垃圾回收
跨组件跟踪是解决跨组件边界的引用循环问题的一种方法。只要组件可以形成具有跨API边界的非平凡所有权的任意对象图,就会出现此问题。CCT的增量版本在V8和Blink中实现,从而以安全的方式有效且高效地回收内存。
David Chisnall - C 不是一种低级语言
鉴于最近的 Meltdown 和 Spectre 漏洞,值得花一些时间来研究根本原因。 这两个漏洞都涉及到处理器推测执行指令,绕过了某种访问检查,并允许攻击者通过侧信道观察结果。 添加导致这些漏洞以及其他几个漏洞的功能,是为了让 C 程序员继续相信他们正在用低级语言编程,但事实上这种情况已数十年如此。
Robert C. Seacord - 未初始化读取
大多数开发人员都理解,在 C 语言中读取未初始化的变量是一种缺陷,但有些人依然这样做。 在当前版本的 C 标准 (C11) 中,读取未初始化对象时会发生什么尚无定论。3 已经提出了各种提案,以在计划中的 C2X 标准修订版中解决这些问题。 因此,现在是了解现有行为以及标准提出的修订建议,从而影响 C 语言演进的好时机。 鉴于 C11 中未初始化读取的行为尚无定论,谨慎的做法是从代码中消除未初始化读取。