使用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代码中识别属性/特性,这里的