使用Semmle QL进行漏洞挖掘:DOM XSS攻击分析
在之前的博客文章(第一部分和第二部分)中,我们讨论了如何在C和C++代码库中使用Semmle QL来发现整数溢出、路径遍历和导致内存损坏的漏洞。在本文中,我们将探讨如何将Semmle QL应用于Web安全,寻找最常见的客户端漏洞之一:基于DOM的跨站脚本(XSS)攻击。
您可能已经知道,XSS攻击可以是服务器端或客户端的。大多数Microsoft服务构建在ASP.NET之上,而客户端则大量基于TypeScript和JavaScript。本文将介绍在目标上确定源(输入的起点)和接收器(输入可能到达的位置,可能被利用为漏洞)的过程,以及如何消除误报/不可利用的情况,以减少代码审计的时间投入。我们的目标是Outlook Web(outlook.office.com),它是Office 365的一部分。
注意:在阅读本文之前,建议您阅读本系列的前几部分,以了解一些基本概念,特别是数据流分析和污点跟踪。
定义源
源是应用程序接收用户提供数据的地方。从攻击者的角度来看,有趣的源是那些他们可以在受害者端轻松控制的地方。例如,攻击者不太可能通过仅仅诱骗受害者访问恶意页面来控制HTTP请求中的User-Agent字段,这使得它成为一个不有趣的源。
在初步审查中,我们观察到源可能来自多个地方,例如HTML元素属性、HTTP请求头、API响应、跨源消息通道或URL本身(location.search、location.href、location.hash、location.pathname)等。
缩小源的范围非常困难、昂贵,并且有可能错误地丢失假阴性案例。相反,我们可以稍后通过重写Dataflow库的isSanitizer谓词来评估和消除不可利用的路径。
此外,污点跟踪将用于跟踪这里的流,因为我们的数据可能流经一些最终会污染其他节点的地方(第二部分提到)。例如,对应于字符串“Hello World”的数据流节点应该污染res变量。
|
|
让我们通过以下模型在TaintTracking配置中定义源,使其简单且广泛开放:
|
|
定义接收器
由于Web世界的复杂性,XSS接收器非常丰富。通常,我们需要在开始编写查询之前手动审查并获取一些目标知识。一段时间后,我们应该准备好列出一些有希望的接收器来执行分析。在本文中,我们将重点关注三个最大且众所周知的接收器:Location、Document和ReactJS。
Location接收器
Location接收器是用户浏览器通过各种方式导航到其他地方的地方(见下图)。由于常见的向量是注入javascript: URI方案,这些可能容易受到XSS攻击,这会使浏览器执行JavaScript代码。
赋值 | 方法调用 |
---|---|
location = “javascript:alert(document.domain)" window.location = “javascript:alert(document.domain)" document.location = “javascript:alert(document.domain)" location.href = “javascript:alert(document.domain)” |
open(“javascript:alert(document.domain)”) window.open(“javascript:alert(document.domain)”) location.assign(“javascript:alert(document.domain)”) location.replace(“javascript:alert(document.domain)”) |
注意:与HTML元素和其他相关的接收器不在本文的讨论范围内。
让我们开始吧。
首先,我们寻找一个赋值,其左侧是全局对象location,右侧值是我们感兴趣的接收器节点。location对象引用可以谓词到DataFlow::globalVarRef(string name),以访问名为name的全局对象。
以下查询揭示了window.location=…和location=…,因为它们都作为全局对象暴露。
|
|
下一步是找到剩下的两个赋值:document.location=…和location.href=…
显然,这两个都是将值写入对象属性的表达式。DataFlow::SourceNode提供了一个名为getAPropertyWrite(string prop_name)的谓词来帮助我们跟踪所有写入属性prop_name的节点。谓词getAPropertyWrite返回一个DataFlow::PropWrite,因此我们需要定义一个来获取它,然后让接收器节点成为这个赋值的右侧。
通过添加另一组接收器节点,如以下QL所示,我们能够识别所有以赋值语句形式形成的location接收器。
|
|
下一步是识别形成函数调用的location接收器。我们获取对名为open的全局对象的数据流节点引用。特别是,以下QL查询列出了目标为open(…)和window.open(…)的调用。
|
|
除了getAPropertyWrite,DataFlow库还提供了一个名为getAMethodCall的谓词,用于查找SourceNode(GlobalVarRefNode的超类型)上的所有方法调用。以下是一个查询,用于定位对location.assign或location.replace的任何调用。
|
|
将所有这些放在一起,识别此类接收器的最终查询如下所示:
|
|
Document接收器
与上一个类似,让我们将这种类型的接收器分为两种形式,如下所示:
赋值 | 方法调用 |
---|---|
element.innerHTML = “ element.outerHTML = “ |
document.write("<img src=a onerror=”,“alert(1)>”) document.writeln("<img src=a onerror=”,“alert(1)>”) node.insertAdjacentHTML(“afterend”," jquery_method.html(" |
注意:除了.html()之外,还有许多jQuery方法接受HTML字符串。然而,在这个代码库中,我没有观察到这些方法的大量使用,而且它们看起来并不容易受到攻击。
首先,我们寻找任何将值写入对象的innerHTML或outerHTML属性的数据流节点。这可以通过一个简单的查询完成:
|
|
在“方法调用”部分,采用与上一个接收器类似的方法,我们感兴趣的是全局DOM对象document的write和writeln方法的调用的第一个参数。另一方面,insertAdjacentHTML和html调用的第二个和第一个参数分别是我们正在寻找的接收器节点。
最后,我们根据上述条件定义一个接收器,使用查询:
|
|
注意:我们使用内联转换.(JqueryMethodCall),内置在javascript库之上,以整齐地削减任何不对应于JQuery方法的调用节点。因为JQueryMethodCall是一个表达式,在进行转换之前,我们需要暴露数据流节点call的表达式。另外,因为write和writeln可以接受多个参数作为HTML字符串,我们让argPos对document方法未指定,以使getArgument捕获所有它们。
ReactJS XSS接收器
在OWA代码库中,开发人员还采用了ReactJS,这是一种快速且方便的构建用户界面的方式。因此,对于这种情况,还有另一个接收器需要处理。
dangerouslySetInnerHTML是一个属性,让开发人员在渲染(称为JSX)时将HTML字符串直接推送到React元素。它在代码库中看起来像这样:
|
|
如代码所示,开发人员使用tslint来确保代码质量,并可能避免一些众所周知的问题。这个React组件负责将HTML表达式写入文档,在内容经过仔细清理之后,因为它可能包含不受信任的数据。
现在,让我们回到QL。幸运的是,内置库有一个模块semmle.javascript.JSX,为我们提供了处理JSX代码的类和谓词。例如,以下查询指示了HtmlContent被使用的地方:
|
|
此外,JSXAttribute类帮助我们在JSX代码中识别属性/属性,这里的目标接收器是作为名为dangerouslySetInnerHTML的JSX属性值传递的对象内部的属性__html的值。将它们关联为接收器如下:
|
|
在尝试确保这种方法一切正常时,我发现有一个缺失的情况,具有不同的代码模式(见下面的例子)。这里值是一个函数调用的返回值。因此,上述查询无法处理这种独特的情况。在调用函数prepareForInnerHTML时,流分析失败:
|
|
为了解决这个问题,我们可以采用稍微不同的方法,通过修改接收器使其更通用:
|
|
此后,我们可以进行一个额外的污点步骤,该步骤将流经对应于内部对象的数据流节点。这里是一个将值写入属性__html的数据流节点。
|
|
使用这个QL,我们应该能够覆盖所有情况。
整理出来
到目前为止,一切顺利。然而,查询揭示了太多记录,包括无风险/不可利用的地方,我们不想花太多时间。这引出了下一个问题;我们如何避免在安全结果上浪费时间?以location接收器为例。对于任何正常站点,如果我们针对查询遍历记录,我们会观察到大量源节点是常量字符串,因此攻击者完全无法控制。没有理由将它们显示在表格中。正如本系列博客的最后一部分提到的,谓词isSanitizer可用于通过在我们满足条件的任何节点上放置清理器来消除我们不感兴趣的路径。
|
|
当我们使用上述定义重新运行分析时,一堆节点被排除。
当我们继续手动审查剩余记录时,我们可以注意到许多节点调用这些函数:getAttachmentUrl、getModuleUrl、getHelpUrl等。它们不太可能被利用,因为它们的返回值总是有一个前缀,看起来像/some_path/…{controlled value}…这个前缀意味着不可能在URL的头部注入javascript: URI方案。此外,它们都遵循相同的模式:get…Url。以下是这些特征的最终模型:
|
|
对其他接收器重复相同的策略,找出非易受攻击代码的模式,然后通过定义清理器移除无趣的节点。这将使安全工程师的生活更轻松,从而扩大代码审查能力,并减少在具有巨大代码库的目标上的努力。
结论
这种方法的一个巨大优势是,我们可以将大部分工作应用于其他目标。QL中有许多类/谓词,我们也可以使用它们来调查其他组件,如HTML元素、AngularJS、Electron等。甚至有一些由Semmle提供的现有配置/查询用于识别基于DOM的XSS问题。然而,在本文中,我想向您展示如何从头开始构建一个,以便您可以自己制作。
作为试图采用Semmle QL的安全工程师,我们应该不断提高查询的质量,使其更准确和智能。也不要忘记从聪明的白帽安全研究人员发现的漏洞中学习。这些可以帮助我们发现新的潜在威胁和接收器。
最后,Semmle QL是一个非常有前途的工具。使用Semmle QL分析Outlook Web App代码库,然后手动跟踪数据流,导致我在88个Location接收器、50个Document接收器和11个ReactJS接收器中发现了两个重要严重性的跨站脚本漏洞。当您可以通过查询找到安全漏洞时,这不是很酷吗?有机会开发创造性解决方案来解决问题。这里唯一的限制可能是我们的想象力。
Luật Nguyễn,MSRC漏洞与缓解团队。