针对Node.js生态系统的隐藏属性滥用攻击

robots

 

如今Node.js凭借其跨平台、高性能的JavaScript执行环境,被广泛应用于服务器端和桌面程序(如Skype)的开发。在过去几年中,有报道称其他动态编程语言(例如 PHP 和 Ruby)在共享对象方面是不安全的。然而,这种安全风险在 JavaScript 和 Node.js 程序中并没有得到很好的研究和理解。

在本文中,通过对 Node.js 程序中客户端和服务器端代码之间的通信过程进行首次系统研究来填补这一空白。本文广泛地识别了流行的 Node.js 程序中的几个新漏洞。为了证明它们的安全含义,本研究设计并开发了一种新颖的可行攻击,称为隐藏属性滥用 (HPA,hidden property abusing)。进一步分析表明,HPA 攻击与现有的关于利用和攻击效果的调查结果略有不同。通过 HPA 攻击,远程 Web 攻击者可以获得危险的能力,例如窃取机密数据、绕过安全检查和发起 DoS(拒绝服务)攻击。

为了帮助 Node.js 开发人员针对 HPA 审查他们的程序,本研究设计了一种名为 LYNX 的新型漏洞检测和验证工具(https://github.com/xiaofen9/Lynx ),该工具利用混合程序分析来自动揭示 HPA 漏洞,甚至合成漏洞利用。将 LYNX 应用于一组广泛使用的 Node.js 程序,并确定了 15 个以前未知的漏洞。目前已经向 Node.js 社区报告了所有发现。其中 10 个被分配了 CVE,其中 8 个被评为“严重”或“高危”。这表明 HPA 攻击可能会导致严重的安全威胁。

 

0x01 Introduction

Node.js 是一个跨平台、高性能的 JavaScript 程序执行环境。它已被广泛用于开发服务器端和桌面应用程序,例如 Skype、Slack 和 WhatsApp。根据最近的一项研究,Node.js 是连续三年(2017-2019 年)各种开发中使用最广泛的技术。Node.js 的突出地位使其安全性变得至关重要。具体来说,一旦发现一个广泛使用的模块存在漏洞,大量的 Node.js 应用程序可能会因重用现象而受到影响。通过利用这些漏洞,远程攻击者可能会在易受攻击的服务器端应用程序中滥用强大的特权 API 来发起严重攻击,例如窃取机密数据或执行任意恶意代码。

Node.js 程序是用动态编程语言 JavaScript 构建的。在过去的几年里,一些动态语言,如 PHP和 Ruby,都面临着常见的安全风险 CWE-915,其中内部对象属性被不可信的用户输入不当修改。尽管有严重的安全后果,但在 JavaScript 和 Node.js 程序中并没有很好地研究和理解这个问题。在本文中,首次系统地研究了 Node.js 程序中客户端和服务器端代码之间的对象共享和通信过程。确认 JavaScript 和 Node.js 程序中也存在上述安全风险。为了证明安全隐患,本文设计了一种名为隐藏属性滥用 (HPA) 的新型攻击,它使远程 Web 攻击者能够获得危险的能力,例如窃取机密数据、绕过安全检查和发起拒绝服务攻击。进一步分析表明,HPA 在许多方面与 PHP和 Ruby的现有发现不同,例如漏洞用和攻击效果。

HPA 攻击示例如上图所示,远程 Web 攻击者向目标 Node.js 服务器程序发送精心设计的 JSON 数据,其中包含额外的意外属性“I2”(称为隐藏属性)。然后,victim 程序正常处理恶意输入负载。最后,I2 传播到内部对象。如红线所示,输入的 I2 覆盖并用冲突名称替换受害者内部对象的关键属性。因此,攻击者可能会滥用隐藏属性的传播过程(即属性传播)来强大地操纵与受感染属性相关的关键程序逻辑,例如通过为输入的 I2 分配适当的值直接调用特权 API(即,”admin”)。

分析表明,受害者属性可以是任何类型,例如关键函数或关键程序状态。由于此特征,输入验证无法阻止攻击者发起 HPA 攻击,因为他们可能会通过覆盖关键状态或删除所有安全检查来禁用验证逻辑。发现这种攻击场景在实践中非常普遍。为了帮助 Node.js 开发人员检测和验证其 Node.js 应用程序和模块中新出现的 HPA 问题,设计并实现了一个名为 LYNX的漏洞检测和验证工具。 LYNX 结合了静态和动态分析的优点来跟踪属性传播,识别隐藏的属性,并生成相应的具体漏洞用于验证目的。

 

0x02 Hidden Property Abusing

A.威胁模型

假设 Node.js 应用程序和模块是良性但易受攻击的。此外,假设目标应用程序正确实现了对象共享(即数据反序列化)。在此设置中,远程 Web 攻击者旨在使用 HPA 破坏易受攻击的服务器端程序。为了利用该漏洞,攻击者通过合法接口向受害应用程序发送精心设计的有效载荷。当恶意负载到达受害应用程序时,它会被视为正常数据并按常规处理。由于输入对象和内部对象之间缺乏严格隔离,恶意负载会传播到易受攻击的 Node.js 模块的内部对象。最后,一个关键的内部对象被破坏并发起攻击。

B.运行示例

为了说明 HPA 攻击,介绍了在热门Node.js 框架“routing-controller”(npm 上每月下载量超过 63,000 次)中发现的真实漏洞。在这个例子中,展示了尽管这个易受攻击的框架对不安全的外部数据强制执行全局输入验证,但攻击者仍然可以利用 HPA 攻击来篡改其验证逻辑并引入任意恶意负载。

上图显示了攻击细节。在第一步中,攻击者在访问受害框架的身份验证 Web API login() 时向输入对象添加了一个额外的属性(即隐藏属性)constructor: false。被调用后,身份验证模块将实例化一个名为 param 的对象并将其发送到参数处理程序,该处理程序负责验证用户输入。为此,图中的函数 transform() 通过将 param 与格式规范对象模式合并来构建验证候选。如第二步所示,在构建这样的候选对象时,隐藏属性 constructor: false 进一步传播到内部对象schema中。

上述传播过程使攻击者能够通过劫持constructor的继承链来禁用输入验证逻辑。在 JavaScript 中,每个对象都有一个指向原型对象的链接。当程序想要访问一个对象的一个属性时,不仅会在对象上搜索该属性,还会在对象的原型上搜索该属性,甚至是原型的原型类型,直到找到一个名称匹配的属性成立。因此,每个对象除了自己的属性外,还有许多继承的属性。但是,如果存在位于搜索树更高级别的冲突名称属性,则可以劫持这样的继承链(注意劫持过程不同于原型污染。

在第三步中,函数validate() 检查候选对象中的所有属性,以查看输入对象是否合法。 validate 内部调用函数 getSchema() 从候选中提取格式规范。然而由于劫持,函数 getSchema() 访问伪造的构造函数(由红色虚线指向)而不是真正的构造函数(由黑色虚线指向)。结果,用于验证的最终格式对象由攻击者通过隐藏属性控制。要绕过输入验证,攻击者只需将格式设置为无效值,例如 false。最后,如第四步所示,攻击者可以让恶意电子邮件通过验证,并进一步对数据库模块进行 SQL 注入攻击。

C.攻击向量

远程攻击者可以传播隐藏属性来篡改某些内部状态。一般来说,有两种典型的攻击向量。第一个称为特定应用程序属性操作(App-specifific attribute manipulation),它涉及篡改应用程序开发人员定义的某些内部属性。二是原型继承劫持(Prototype inheritance hijacking),劫持原型继承链。值得注意的是,第二个攻击向量不同于现有的攻击,如原型污染。原型污染需要对原型进行修改。但是如运行示例所示,HPA 的攻击者不需要篡改原型。

特定应用程序属性操作:此攻击向量针对易受攻击的代码,该代码错误地向用户控制的对象公开某些特定于应用程序的属性(例如,访问权限)。前图中的I2 属性应该由内部函数初始化和管理。但是,使用 HPA,攻击者可能会将同名属性传播到内部对象,从而访问敏感 API。此攻击向量可用于滥用某些服务,例如大型应用程序中的订单状态。

原型继承劫持:此向量劫持原型继承链,以便攻击者可以诱使易受攻击的程序引用用户控制的属性,而不是从原型继承的属性。使用这个向量,攻击者可以伪造许多内置属性,甚至嵌套的原型属性(发现的两个漏洞是使用嵌套属性来利用的)。如有必要,他们还可以伪造其他原型属性,例如constructor.name。这个向量非常有用,因为许多 JavaScript 开发人员倾向于信任从原型继承的属性,并基于它们做出许多安全敏感的决定。

D.HPA与相关攻击比较

在一些动态语言,如Ruby和PHP中,已经发现了修改动态对象属性(CWE-915)不当的风险。本研究是第一个在 Node.js 中识别此类风险的工作。此外,发现 HPA 在多个方面与现有漏洞不同。

上表总结了 HPA 和 Ruby mass assignment 之间的区别,这是 CWE-915 导致的典型漏洞。 首先,它们滥用不同的逻辑来传递有效载荷:HPA 利用对象共享将恶意对象传递给受害程序,而 Ruby mass分配滥用特定于框架的分配功能来修改分配左侧的某些现有属性。其次,HPA 可以引入具有文字值或嵌套对象的隐藏属性,而质量分配有效载荷仅仅是文字值。第三,由于 Ruby 是一种强类型语言,大量赋值漏洞无法为受害对象创建新属性。然而,JavaScript 更灵活,因此 HPA 可以向受害对象注入任意属性,甚至允许隐藏属性在到达目标对象之前通过多个变量传播。运行示例是这样的:隐藏属性构造函数从输入对象传播到内部模式对象以攻击输入验证逻辑。

值得注意的是,CWE-915 的漏洞不是反序列化错误(CWE-502)。 具体来说,CWE-915 的范围更窄,用于对象修改,并不一定利用反序列化过程。 例如,HPA 不攻击对象反序列化的逻辑。 相反,它旨在修改内部对象的属性。

 

0x03 LYNX Design and Implementation

A.定义

隐藏属性:给定一个模块,它包含一个输入对象 Oinput 和一个内部对象 Ointernal。仅当满足以下所有三个要求时,Oinput 中才存在隐藏属性 Phidden:

• Phidden 属于Ointernal 并且在模块中被引用。

• 如果在 Oinput 中添加了具有相同名称(即 Phidden)的冲突属性,则可以修改 Ointernal 的 Phidden。

• Phidden 不是Oinput 的默认参数。这意味着在使用默认参数调用模块时,Oinput 的 Phidden 不会被初始化。

为了帮助描述问题,使用“属性载体”(property carrier)来表示所有带有隐藏属性的变量(包括 Ointernal 和 Oinput)。

有害的隐藏属性:如果攻击者可以滥用隐藏属性将意外行为引入模块,则该隐藏属性被认为是有害的。在本文中,从以下三个方面考虑潜在的攻击效果:

• 机密性:隐藏的属性可能会导致敏感信息在被滥用时泄露。

• 完整性:攻击者可能会破坏模块中关键属性的一致性或可信度。

• 可用性:攻击者可能会违反应用程序对属性的期望,由于意外错误情况导致拒绝服务攻击。

B.挑战与解决方案

本研究目标是设计和开发一个端到端系统,可以自动有效地检测目标 Node.js 程序上的 HPA 安全问题。然而,由于以下两个挑战,这不是一项简单的任务。

C1.如何发现 Node.js 程序的隐藏属性?

现有技术无法完美解决这个问题。特别是,静态分析可以轻松获得目标程序的全貌,但通常会引入很高的误报,尤其是在处理指向和回调问题时,发现这种情况在 Node.js 程序中非常常见。动态分析,如数据流跟踪,适用于 1) 跟踪输入对象及其所有传播,以及 2) 发现和标记相关的属性载体,并将其对应的属性视为潜在的隐藏属性。然而,在实践中发现动态跟踪经常遗漏许多关键执行路径和隐藏属性,从而导致漏报。

解决方案:本文设计了一种混合方法,利用动态和静态分析的优势来发现隐藏的属性。首先利用轻量级标签系统动态跟踪输入对象和相关属性载体,并将属性载体的所有属性作为隐藏属性候选的一部分转储。为了发现尽可能多的执行路径,尤其是关键路径,递归地广泛标记输入对象并测试目标程序。其次,上述动态测试不可避免地会造成漏报。发现在许多情况下,即使成功标记了相应的属性载体,关键的隐藏属性仍然被忽略。为了缓解这个问题,通过贪婪地搜索可能被忽略的属性来引入静态分析。最后,收集结果并获得隐藏属性候选列表。

C2.在海量的隐藏属性中,如何确定哪一个是有价值的、可被攻击者利用的?

本文发现在收集到的隐藏属性候选者中,并非所有候选者都有价值且可被攻击者利用。其中许多甚至不会造成任何攻击后果,因此应将其过滤掉。此外,已识别隐藏属性的相应值通常具有特定要求和约束。因此,给定一个隐藏属性候选者,攻击者需要确定其危害性并计算其对应的值。

解决方案:利用符号执行来探索所有相关路径,收集路径约束,检测敏感行为,并最终生成漏洞利用。

C.设计概述

LYNX 架构的概述如下图所示,本文方法有两个方面。在第一阶段,LYNX 首先动态运行一个标签系统,用于递归跟踪输入对象,并识别尽可能多的属性载体。通过检测目标 Node.js 代码来实现动态标签系统,然后通过使用常规输入数据(例如测试用例)触发其 API 来执行检测的代码。然后,LYNX 通过收集上述动态分析结果并应用静态分析来搜索忽略的隐藏属性,从而获得候选隐藏属性。特别地,LYNX 将前面动态分析步骤中记录的必要信息单元化,分析目标 Node.js 程序的 AST(抽象语法树),并检测与属性访问相关的操作。最后,根据观察修剪结果。

在第二阶段,LYNX 首先生成带有检测到的隐藏属性候选者的漏洞利用模板。然后,LYNX 运行符号执行来推理隐藏属性的值并验证相应的危害和攻击后果。

D.识别隐藏属性

(1)发现属性载体

通过检测目标 Node.js 程序来实现动态分析。在本节中,首先介绍标记和跟踪输入以及检测属性载体的检测细节。然后,讨论如何驱动和执行检测的代码。

标记和跟踪输入:为所有输入对象添加标签以跟踪它们。新添加的标签是一个新的属性,它有一个唯一的键值对。例如,假设输入对象 Oinput = {“email”:”a@ gmail.com”},LYNX 使用新属性检测 Oinput。因此,新的输入对象 O’input 是 {“email”:”a@ gmail.com”, unique_key: unique_value}。

当 Oinput 具有简单的数据结构时,上述简单的标签添加过程有效。但是,当 Oinput 很复杂时,这种方法是不够的。例如,当 Oinput 具有多个属性(例如 Oinput .a 和 Oinput .b)时,这些子属性可能会随着不同的程序状态而以不同的方式传播。如果只为 Oinput 添加一个标签,将无法跟踪所有这些子属性。因此,LYNX 遍历 Oinput 并递归地将标签注入不同的子属性。例如,考虑上面具有两个属性的 Oinput,LYNX 分别在 Oinput、Oinput .a 和 Oinput .b 的基数中注入了三个不同的标签。

标记方法在检测属性载体方面优于经典的数据流跟踪(即不改变输入的透明跟踪),因为它更好地模拟了 HPA 的攻击过程。例如,在某些情况下,被测试的程序包含一个按类型分配输入的调度程序。在分析此类情况时,LYNX 会以与真实攻击过程相同的方式修改输入。如果修改改变了输入类型,输入可能会触发另一个路径。但是,经典方法可能仍会跟踪原型输入的路径。因此,本文方法可以更准确地确定真实 HPA 负载可能触发的真实执行路径。

但是,改变原始输入也可能带来负面影响。例如,假设有一个检查函数来清理输入的某个属性,如果 LYNX 为该属性添加了一个标签,程序可能会引发错误并退出。为了缓解这个问题,LYNX 应用了一次一个标签的策略。在每一轮分析中,LYNX 只为其中一个属性添加一个标签,然后多次重复此步骤以测试所有属性及其子属性。

识别属性载体:在向输入添加标签后,LYNX 使用新输入执行程序并观察标签属性如何传播。如果 LYNX 发现标签传播到内部对象,它会将宿主对象标记为属性载体。为此,通过拦截所有变量读/写操作来检测目标 Node.js 程序。当这样的操作发生在一个内部对象上时,LYNX 会递归地检查这个对象的所有属性和子属性。如果检测到一个标签,这个对象会被标记为属性载体,格式如下:< O,L,S >,其中O记录属性载体的对象名称,L指向包含检测对象的JavaScript文件,S记录载体的可见范围。在 LYNX 中,“.”用于通过连接不同的函数名称来表示作用域。为了区分函数对象和变量对象,在函数类型作用域中添加了特殊的后缀_fun。有关范围表示的更多详细信息如下图。

运行动态分析:LYNX 根据它们的类型运行检测的目标 Node.js 程序。 更具体地说,如果应用程序是基于 Web 的程序(例如 Web 应用程序),则 LYNX 会直接运行它。 如果目标 Node.js 代码在 Node.js 模块中,LYNX 需要将其嵌入到一个简单的 Node.js 测试应用程序中。 然后,LYNX 调用目标 Node.js 模块的公开 API。 但是,在这种情况下,LYNX 需要为 API 提供一些适当的输入,这通常很难自动生成。 基于以下观察缓解了这个问题:发现大多数 Node.js 模块都是随用例一起发布的(npm上 50 个最依赖的包中有 45 个具有直接可用的测试用例)。 因此,LYNX 可以直接使用它们来驱动分析。

对于触发 API,LYNX 目前支持两种类型的对象共享方案。首先是JSON序列化,这也是最常用的方法。第二种方法是查询字符串序列化。在 Node.js 生态系统中,许多请求解析模块也支持将 URL 查询字符串传递给对象。例如,一个名为 qs 的请求解析模块(在 npm 上每月下载 1 亿次)将查询字符串转换为单个对象(例如,从 ?a=1&b=2 到 {a:1,b:2})。 LYNX 通过记录和重放 Web 请求来检测查询字符串中的隐藏属性。

运行示例:为了说明 LYNX 如何识别属性载体,重新审视运行示例。如下图所示,注入的标签属性沿黑色虚线的路径传播。通过跟踪此流程,LYNX 识别出三个属性载体(值、参数和对象)并为每个属性记录载体实体。举一个实体的例子,展示了对象的实体是如何合成的:首先,为了得到 O,LYNX 检查标签属性的标识位置。在这种情况下,标签属性是从对象的基础标识的。因此,LYNX 直接将 O 设置为“对象”。其次,要获取L,LYNX 获取当前脚本的文件路径。第三,为了得到S,LYNX提取了载体的可见范围。在这种情况下,载体是从位于第 10 行到第 22 行的匿名函数中找到的。因此,LYNX 将可见性编码为 anon.10_1.26_1_fun。总的来说,记录的实体将是 hobject, script_path, anon. 10_1. 22_1. _funi。

(2)查明隐藏属性候选

动态分析可以有效地检测属性载体。然而,它在检测隐藏属性时不可避免地存在漏报。发现在某些情况下,即使隐藏的属性载体已经被发现,重要的隐藏属性也被忽略了。通过应用静态分析作为补充来缓解这个问题。在本节中首先讨论动态分析出现漏报性的原因。然后介绍静态分析的设计细节。最后,讨论如何修剪分析结果。

静态分析的必要性:为了解释动态分析的弱点,使用了一个虚拟的易受攻击的代码示例List 1(摘自真实代码)。在这个例子中,函数 foo() 根据用户控制的变量输入(第 2 行)构建了一个内部变量 conf,这使得 conf 成为一个属性载体。动态方法可以捕获 propertyA,但如果不满足条件,它将错过 propertyB。为了解决这个问题,LYNX 实现了一个过程内静态句法分析,可以识别索引语法,无论实际代码是否被执行。

提取隐藏属性候选:给定一个隐藏属性载体“< O,L,S >”,LYNX 首先在相应的 AST 中识别它(由 L 指向)。 LYNX 搜索 S 中记录的可见性范围内的所有对象引用。最后,LYNX 查明所有属于 O 子属性的引用,并将它们标记为隐藏属性候选。由于以下原因,子属性是潜在的隐藏属性:报告属性载体 < O,L,S > 是因为标签属性可以传播到变量 O。因此,O 下的其他属性也可能被伪造/ 从输入覆盖。请注意,由于贪婪策略,并非所有在这里找到的罐子都可以使用输入来操作。因此,LYNX 将使用下一个组件来验证每个候选者以确保准确性。

由于 JavaScript 的动态特性,子属性可能以不同的方式被索引。为了提高该模块的检测覆盖率,LYNX总结并认可了以下三种索引方式: (1) 静态索引:用文字类型的键(例如,obj.k 或 obj[‘k’])索引的属性; (2) 函数索引:使用内置函数索引的属性(例如,obj.hasOwnProperty(‘k’))。 (3) 动态索引:用变量索引的属性(例如,obj[kvar])。 LYNX 静态识别前两种方法:它遍历 AST 以恢复索引语义。为了识别第三种方法中的属性,LYNX 从以前的执行跟踪中提取 kvar 的实际值。值得注意的是,由于 LYNX 依赖之前的动态执行跟踪来支持动态索引,因此无法保证 100% 覆盖。也就是说,LYNX 只识别上一步具体索引的动态索引属性。

运行示例:这里仍然使用前图中的示例来说明它是如何工作的。以第 11 行的载体对象为例,LYNX 首先搜索其可见范围内的所有子属性引用(第 10 行到第 22 行的匿名函数),并检测到恰好在确定载体的地方。找到该属性后,LYNX 需要进一步检查输入对象是否可以覆盖该属性。为此,LYNX 检查构造函数是否是 O 的子属性。在此检查通过后,LYNX 将构造函数识别为隐藏属性候选者。

(3)修剪结果

如上所述,隐藏属性候选被发现。然而,发现其中一些是已知参数而不是未知的隐藏属性。这是因为一些 Node.js 模块实现了可选参数作为输入对象的属性。这些记录的属性也可以在上一步中提取。例如,默认情况下,电子邮件模块接受像 {“from”: .., “to”: ..} 这样的输入对象,但也接受更多选项,例如 {“from”: .., “to”: .. ,“cc”:..}。很明显,这些记录在案的参数并不是隐藏的属性。

为了更正结果们引入了一个基于上下文的分析器来自动“推断”所识别的属性是否可以证明是一个记录的参数。分析基于以下观察:记录的参数通常由调度程序一起处理(例如,一系列 if-else 语句)。

基于这一观察,将参数处理过程分为两类:(1)未使用的参数和使用的参数(即原始输入中的属性)由同一个调度程序处理。为了处理这种情况,分析器从公开的 API 的参数中记录使用的属性。然后,它确定与使用的参数位于同一调度程序中的隐藏属性候选。 (2) 未使用的参数和使用的参数由不同的调度器处理。为检测此类参数,分析器会检查所有候选对象,以查看是否从同一调度员处找到了多个候选。如果 LYNX 检测到某些候选匹配任何一种情况,它就会将它们从结果中删除。

E.生成 HPA 漏洞利用

在前面的组件中,LYNX 发现了隐藏属性的关键名称。通过使用这样的关键注入属性,攻击者可能会更改覆盖/伪造某些内部对象。在本节中,利用符号执行来推理发现的属性是否可利用。给定一个隐藏属性候选,首先将其注入到输入中以构建测试负载。由于其对应的值尚未确定,将其符号化。然后,为了确定隐藏属性是否有害,探索了尽可能多的路径并沿着未覆盖的路径精确定位敏感sink。

(1)生成漏洞利用模板

在这一步中,LYNX 旨在生成可以到达潜在易受攻击属性的输入数据结构。将此类结构表示为漏洞利用模板,因为 LYNX 将为每个隐藏属性的值字段指定一个符号值而不是具体值。为了生成模板,LYNX 需要在输入的正确位置插入一个属性(带有发现的关键名称)。为了确定插入位置(应该修改输入的哪个字段),LYNX 维护了标签插入位置和属性载体 O 之间的映射。

为了说明这一点,重用之前讨论的示例:原始输入是 {“email”:”aa@ gmail.com”, “passwd”:”11”}。如前所述,LYNX 需要确定插入位置:根据映射,任何添加到输入基部的内容都会出现在前图中第 11 行对象的基部。然后,LYNX 根据检测到的关键名称。最后生成的模板是{“email”:”aa@ gmail.com”, “passwd”:”11”, “constructor”: SYMBOL}。

(2)利用攻击后果

在为每个隐藏属性候选生成漏洞利用模板后,LYNX 开始分析其潜在的安全后果。为此,LYNX 首先象征性地执行隐藏属性以探索所有可能的路径。然后,LYNX 沿着发现的路径精确定位敏感接收器,以确定隐藏的属性是否有害。

根据有害隐藏属性的定义,从机密性、完整性和可用性三个角度总结出六个敏感sink。如上表所示,不同的 sink 用于检测不同类型的攻击后果。总之,接收器有两种实现方式。第一种类型是基于关键字的接收器。根据观察,敏感 API 的某些参数可能是隐藏属性的常见接收器。因此,通过分析已知漏洞数据库(例如 snyk 漏洞数据库和 npmjs 安全公告)上报告的现有漏洞,收集了一个关键字列表,尽最大努力收集尽可能多的敏感 API。目前,该列表包含 24 个 Sink:11 个文件系统操作 API、9 个数据库查询方法和 4 个代码执行方法(API 列表将与 LYNX 的源代码一起发布)。虽然列表可能不完整,但随着时间的推移,它可以很容易地扩展。另一种类型的接收器是基于行为的接收器。许多漏洞高度依赖于代码上下文。为了识别此类漏洞,关注可能滥用应用程序逻辑的行为。

目前,LYNX 已经覆盖了以下三种恶意行为: (1) 返回值操作,对于旨在操纵临界状态的漏洞,LYNX 会检查被测试模块的返回值。如果攻击者可以控制其返回值,LYNX 会将其标记为易受攻击。 (2)全局变量篡改,如果 LYNX 检测到某个隐藏属性可以篡改某个全局变量,则会将其报告为潜在漏洞。 (3) 循环变量操作,对于旨在通过造成无限循环来破坏服务的漏洞,LYNX 会检查循环条件以确定它们是否可以通过隐藏属性进行操作。

识别出敏感接收器后,LYNX 准备概念验证漏洞利用,旨在验证接收器是否可达到攻击控制值。为了收集漏洞利用,使用上一步生成的输入重新执行程序。如果可以到达接收器,则将输入与攻击指示符一起报告。攻击指标旨在帮助安全分析师了解漏洞利用如何影响接收器。对于不同的接收器,LYNX 采用不同的规则来生成指标。对于基于关键字的接收器,LYNX 会记录可以到达敏感函数/属性的内容类型。对于基于行为的接收器,LYNX 会比较攻击输入和良性输入的执行轨迹,以查明漏洞利用的影响。例如,LYNX 监控全局对象的变化以观察 A1 的可利用性。

整个攻击探索方法总结在算法1中。搜索方法的输入是测试程序m和上一步生成的漏洞利用模板集T。该方法的输出是由(E,I)表示的攻击概念证明,其中E是最终漏洞利用的集合,I是相应的攻击效果指标。在算法的第一阶段,它收集符号执行期间发现的新路径,并将具体输入和路径提取到 U 中。在第二阶段,算法检查每条路径 Pi。检测到敏感sink后,会生成对应的exploit到达sink。如果 LYNX 检测到接收器可达,则 LYNX 将同时报告漏洞利用 exp 和攻击后果指标 ind。

为了演示整个过程,将算法应用于运行示例。如前图所示,LYNX 在第 14 行符号化隐藏属性构造函数。在执行期间,由于蓝色虚线指示的符号值传播,另外两个变量也被符号化。通过解析三个符号值的约束,LYNX 找到了两个可能的路径(即第 19 行和第 21 行)。由于新路径导致最终模块返回值(即对象或空值)的更改,因此漏洞利用命中 I2 。因此,LYNX 构建了一个漏洞利用 {“email”:SQLI, “passwd”:”11”, “constructor”:false}(SQLI 代表 SQL 注入负载)。将漏洞利用程序输入程序后,LYNX 会收集相应的指标:它检测到可以通过将构造函数设置为 false 来更改返回值。

F.实施

将 LYNX 构建为一个 Node.js 应用程序,并通过使用几个现有工具来实现它。 在 LYNX 的第一个分析阶段(即识别隐藏属性),使用 Jalangi来检测目标 Node.js 代码以实现标签系统。带有标签的检测 Node.js 代码会动态执行以发现隐藏的属性载体。 应用 Esprima生成 AST(抽象语法树),用于对已识别的属性载体进行静态分析并提取隐藏属性。 在 LYNX的第二个分析阶段,使用 ExpoSE执行符号执行,以确定发现的隐藏属性的危害性并生成漏洞利用。为了分析基于 Web 的应用程序,实现了一个基于分析的 pipeline,用于捕获 HTTP 请求并生成相应的测试用例。

 

0x04 Evaluation

为了评估 HPA 的安全影响,将 LYNX 应用于一组在实践中广泛使用的真实 Node.js 应用程序和模块。在以下部分中,将通过三个研究问题讨论评估结果:

• RQ1:隐藏属性是否普遍存在于广泛使用的 Node.js 程序中?

• RQ2:LYNX 能否有效检测有害的隐藏属性并生成相应的漏洞利用?

• RQ3:发现的漏洞和利用如何扩大Node.js 生态系统的攻击面?

A.数据集

Node.js 取得了很大的进步,并且已经有很多 Node.js 程序可用。但是,发现其中大部分很少使用或与威胁模型不匹配。因此,为了减少分析的工作量,规范了数据集收集过程。特别是,根据以下两个标准收集 Node.js 程序:(1)被测试的程序应该用于与外部输入交互,并且它们的 API 应该接受对象(通过 JSON 或查询字符串序列化)。 (2) 被测试的程序应该被广泛使用或持续维护。

为了满足第一个标准,从最有可能暴露于输入的类别中收集程序。这些类别包括数据库、输入验证、用户功能和基于 Web 的应用程序/中间件。为了满足第二个标准,从知名供应商(例如 MongoDB)收集程序,以及在 Github 上至少有 1000+ star 或在 npm 上每月下载 500 次的项目。

总共收集了 102 个 Node.js 程序作为分析数据集。有 91 个 Node.js 模块和 11 个基于 Web 的程序。在 11 个基于 Web 的程序中,4 个是最小的 Web 框架/中间件,7 个是完整的 Web 应用程序。

B.分析结果

(1)概述

在配备 Intel Core i5-9600K (3.70GHz) 和 32 GB 内存的 Ubuntu 18.04 机器上运行 LYNX。总共检测到 451 个隐藏属性候选,并确认了 15 个以前未知的 HPA 漏洞。截至撰写本文时,已为发现分配了 10 个 CVE。其中半数以上被NVD(美国国家通用漏洞数据库)评为“严重”和“高危”。

在这些漏洞中,有两个是从完整的 Web 应用程序中识别出来的。其他 13 个漏洞是从模块中识别出来的,总共影响了 20,402 个相关应用程序/模块。 Node.js 社区非常关注本研究的发现。权威的公共漏洞数据库创建了一个新的概念来跟踪相关漏洞。

(2)阶段 1:识别隐藏属性

为了回答 RQ1(流行的 Node.js 程序中是否普遍存在隐藏属性?),分析了从广泛使用的 Node.js 程序中检测到了多少(以及什么样的)隐藏属性。

下表总结了检测结果(上表列出了完整的检测结果)。在下表中,从第二个列 “Tested Programs”,可以观察到隐藏属性广泛存在于所有可能暴露于外部输入的类别中。总体而言,69% (70/102) 的测试程序被发现包含隐藏属性。

“Detection Results”下的前两列表示属性载体隐藏属性候选者的数量。 LYNX 通过分析 3175 个属性载体,总共确定了 451 个隐藏属性候选。可以观察到隐藏属性候选广泛存在于数据集的所有类别中。 “Detection Results”下的最后一列显示有多少候选者被 LYNX 识别为记录在案的论据。为了弄清楚记录的参数推断规则的正确性,将来自官方文档的记录参数与本文结果进行比较。发现基于上下文的规则可以正确识别来自已识别隐藏属性的所有文档参数。

请注意,根据正在测试的 Node.js 程序的类型来驱动本文的分析。对于91个npm模块,直接重用它们 npm 主页上提供的用例作为测试输入。对于剩下的 11 个基于 Web 的程序,手动与应用程序交互并使用基于分析的pipline生成测试用例。 LYNX 分析 Web 基础程序的 JSON 和查询字符串序列化通道。这 11 个基于 Web 的程序中有 7 个同时支持查询字符串和 JSON 序列化(在不同的 API 中)。

(3)阶段2:探索攻击后果

从以下两个方面评估 LYNX 的有效性(RQ2):

(1)LYNX 是否有效地从不同类别的程序中找出潜在的漏洞?

(2) LYNX 是否成功生成可以直接或轻松移植以引入现实世界攻击效果的漏洞?

下表显示了第二阶段的总结利用结果。在此表中,“Reported”列记录了 LYNX 报告有多少敏感接收器易受攻击。 “Exploitable”列表示 LYNX 自动利用并手动确认为真正漏洞的报告接收器的数量。从这两列中,可以观察到 LYNX 能够从不同类型的程序中查明潜在的易受攻击的接收器。此外,报告问题的“quality”很好。总体而言,发现报告的 15 个漏洞中有 11 个被确认为易受攻击,其他 4 个被认为是无害的。在这4种情况中,虽然一些隐藏属性确实导致了某些敏感的sink,但它们仍然受到程序语义的约束,因此不能引入显着的攻击效果。例如,当 LYNX 利用验证库中的隐藏属性时,它会导致执行异常,从而触发接收器sink I2(最终结果操作)。但是,由于该异常稍后由程序处理,因此它不会启用任何攻击效果,例如验证绕过。

上表的最后一列(“Missed”)记录了 LYNX 成功检测到(阶段1)但未能生成可用漏洞(阶段2)的隐藏属性。为了找出此类隐藏属性,手动检查了 LYNX 报告的所有隐藏属性候选。有三种类型的故障。首先,一些隐藏的属性有一个特定的约束,在代码语义中没有出现。例如,taffyDB(一种流行的 JavaScript 数据库)有一个隐藏属性,可以通过伪造作为内部索引来泄漏任意数据。然而,与索引相关的约束是在内存中而不是在代码中。因此,即使索引采用易于猜测的格式(例如,T000002R000001),LYNX 也无法构建有效索引。这种失败源于符号执行的限制。为了覆盖此类故障,模糊测试技术可能是一个很好的补充,可以覆盖符号执行无法分析的部分。

另一种类型的失败是由多约束问题引起的:为了利用某些隐藏属性,必须将输入的某些参数设置为某些值。可以通过扩展 LYNX 来同时探索多个变量(不仅是隐藏的属性,还有记录的参数)来解决此类故障。最后一种失败来自语法不兼容问题。不兼容是因为底层检测框架 (Jalangi) 与 ECMAScript 6 之后的某些语法不兼容。通过使用 Babel向下编译不兼容的程序或避免检测不兼容的代码来缓解这个问题。为了简化解决不兼容问题的过程,构建了一个自动向下编译工具,该工具将与 LYNX 一起发布。

C.已识别 HPA 漏洞的影响分析

在本节中,试图通过了解 HPA 漏洞如何将严重的攻击影响引入 Node.js 生态系统来回答 RQ3。如下表所示检测到 15 个 HPA 漏洞。为了修复这些漏洞,进行了负责任的披露并通知了供应商。他们立即反应过来。目前已有10家厂商确认该漏洞,其中7家已发布相应补丁。接下来,将从以下三个角度解释HPA的安全影响。

机密性:发现 4 个已识别的漏洞(即 HP-1、HP-2、HP-3 和 HP-14)影响程序的机密性(例如,从数据库中泄露敏感信息)。 HP-1 和 HP-2 漏洞来自两个广泛使用的 mongoDB 驱动程序。通过利用HP-1和HP-2,攻击者可以强制数据库无论查询条件是否正确,始终返回data/true。这可能会被滥用来泄露敏感信息或绕过访问控制。例如,攻击者可能会通过强制身份验证结果为真来登录其他用户的帐户。漏洞HP-3 来自taffyDB。这是一个严重的通用 SQL 注入,可以被滥用来访问数据库中的任意数据项:发现隐藏属性可以伪造为 taffyDB 的内部索引 ID。如果查询中找到索引ID,taffyDB会忽略其他查询条件,直接返回索引的数据项。此外,索引 ID 采用易于猜测的格式(例如,T000002R000001),因此攻击者可以利用此漏洞访问数据库中的任何数据项。漏洞 HP-12 来自电子商务 Web 应用程序 cezerin。发现隐藏属性可以修改存储在数据库中的关键数据(即支付状态为支付)。

完整性:发现了 10 个已识别的漏洞(即 HP-4、HP-5、HP-6、HP-7、HP-8、HP-9、HP-10、HP-11、HP-12 和 HP- 13) 损害 Node.js 应用程序的完整性。 4 个广泛使用的输入验证模块受到 HPA 的影响。正在运行的示例类验证器 (HP-4) 允许攻击者覆盖格式架构对象,从而绕过任意输入验证。 Jpv(HP-5 和 HP-6)检查其原型上不安全对象的类型。但是,由于 HPA 可以修改原型中的属性,因此可以操纵 jpv 的验证结果。其他三个验证绕过漏洞来自 valib 的一个 API(HP-6)和模式检查器的两个 API(HP-7 和 HP-8):通过修改不安全对象原型下的hasOwnProperty 函数,可以跳过安全检查。请注意,这三种情况的利用场景有限:攻击者需要传递有效的函数定义,这不是一个广泛支持的特性。

影响程序完整性的其他 4 个漏洞(HP-10、HP-11、HP-12 和 HP-13)来自用户功能模块。这4个漏洞的利用方式类似:通过操纵输入对象下的一些关键属性,攻击者可以操纵模块调用的最终结果。这种操纵可能会给应用程序带来严重的风险。例如,根据 Github 的 1,822,028 个项目中使用的对象克隆模块 clone-deep 使用易受攻击的种类 (HP-13) 在克隆之前执行类型检查。如果要克隆的变量var被检测为数组,clone-deep递归调用自身var.length次来克隆var下的所有元素。使用 HP-13,恶意对象可以伪造为一个非常长的数组。在克隆这样一个对象时,clone-deep 会进入一个超级大的循环,从而冻结整个应用程序(耗时任务由于其单线程模型可能会阻塞 Node.js 应用程序)。

可用性:发现 1 个网络框架工作(即 HP-15)的可用性会受到 HPA 的影响。此漏洞是从基于 Web 的应用程序 mongo-express 中检测到的。发现隐藏属性可以给应用程序引入无限循环,从而阻塞整个应用程序。

社区影响:本文发现已得到 Node.js 社区的证实。为了帮助开发人员意识到这种新风险,提出了一个新的概念来描述和跟踪相关问题。由 snyk 维护的权威公共漏洞数据库已接受该提议并开始在相关安全问题中使用该概念。

评论:基于影响分析,假设 HPA 攻击确实扩大了 Node.js 生态系统的攻击面。该主张得到以下两个观点的支持。:(1) HPA 攻击通过建立对应用程序内部对象的意外数据依赖性,有效地破坏了以前无法访问的程序状态,并引入了不同类型的攻击效果。 (2) 经典防御技术(例如,输入验证)不能减轻 HPA。如前表所示,一些广泛使用的验证模块容易受到 HPA 攻击。

D.分析覆盖率和性能

根据 ExpoSE的覆盖率监控测量每个 Node.js 程序的 LYNX 代码覆盖率,它计算‘LoC being executed’/‘total LoC in executed fifiles’(不计算依赖项)。在下面讨论覆盖率测量结果,基于不同类型的经过测试的 Node.js 程序:模块和基于 Web 的程序。

对于 Node.js 模块,代码覆盖率各不相同(即 10% – 80%)。虽然大部分模块实现了不错的覆盖率(超过 40%),但认为代码覆盖率并不一定表明 LYNX 的有效性:为了找到实际漏洞,有选择地测试与威胁模型匹配的 API(可能会暴露于外部用户和接受对象)。因此,即使测试用例适用于大多数 API,也不会盲目地测试所有 API。例如,如果某个 API 根本不接受参数,将不会将其包含在测试中,并且此类 API 测试贡献的代码覆盖率无助于从测试程序中审查 HPA。

对于基于 Web 的程序,LYNX 平均实现了 21% 的代码覆盖率。发现这是因为 Web 应用程序通常具有大量的函数/API,而基于分析的测试可能无法涵盖所有这些。为了帮助 LYNX 发现更多的 Web API,整合主动 Web 扫描器 可能是一项很有前途的未来工作。

除了代码覆盖率,还测量了每个阶段的运行时间。作为离线工具,LYNX 实现了合理的分析速度:对于隐藏属性的检测,分析一个 API 的时间通常不超过 10 秒(90% 的情况)。对于非常大的程序,例如 Web 应用程序,每个 API 的分析时间可能超过 200 秒(不超过 10 个案例)。为了利用隐藏属性,需要更长的时间,因为 LYNX 需要为每个候选者探索多条路径。通常,每个隐藏属性需要大约 50 秒。

E.案例研究

访问机密用户数据:LYNX 报告来自 mongoDB Node.JS 驱动程序的有害隐藏属性 (_bsontype)。此属性用于决定查询类型,不应由输入提供。但是发现mongoDB允许input通过HPA修改这个属性。由于 mongoDB 根据预定义的类型处理查询对象。

攻击者可以指定一个未知的 _bsontype(例如 aaa)来强制 mongoDB 不序列化某些对象。例如,这可以被滥用以强制查询结果始终为真(即,不序列化查询文件管理器)。通过利用该漏洞,攻击者可以对 mongoDB 中的机密数据进行未经授权的访问。

为了演示其中一种攻击媒介,使用 Phaser Quest,这是一款使用易受攻击的 mongoDB 驱动程序模块的在线游戏。如List 2 所示,程序通过用户提供的秘密标识符 (id) 加载/删除用户配置文件。通过滥用所讨论的漏洞,攻击者可以强制数据库返回有效用户,而不管标识符是否正确。通过这样做,攻击者可以登录/删除任意玩家的帐户。
已经向 MongoDB 团队进行了负责任的披露。他们已经修补了漏洞并在他们的安全建议中承认了本研究。

阻止事件处理程序:由于 Node.js 基于单线程模型,因此其事件处理程序的可用性非常关键,并且已经讨论了很多。在第二种情况下想演示 HPA 如何攻击事件处理程序,从而冻结整个程序。

LYNX 报告了来自基于 Web 的 mongoDB 管理界面 mongo-express 的有害隐藏属性 (toBSON)。通过滥用此属性,经过身份验证的用户会发出一个耗时的任务来阻止 Node.js 的事件处理程序。如List 3 上半部分所示,在第 3 行识别出一个隐藏属性 toBSON。通过跟踪该属性的数据流,发现它到达了第 12 行的敏感接收器,该接收器用于执行中的代码一个沙箱。因此,攻击者可以通过一个耗时的函数(例如,无限循环)来阻止事件处理程序。

在收到漏洞报告后,项目团队立即确认并将此问题添加到他们的安全公告中。在撰写论文时正在与他们合作修复错误。

 

0x05 Discussion

缓解对策:总结了针对 HPA 的三个主要对策。例如,其中之一是验证输入对象。由于 HPA 的第一步是注入其他属性,因此删除不需要的(恶意)属性可能是一种可行的缓解措施。

局限性:首先,LYNX 需要外部输入(即模块测试用例或网络上的用户交互)来触发分析。由于不同模块/应用程序的 API 具有不同的上下文依赖关系和参数格式,因此很难自动推断和解决这些先决条件。例如,在评估过程中,发现需要登录被测试的 Web 程序才能访问某些 API。为了解决这个问题,实现了一个pipline,可以自动重放和改变 API 调用。为了测试基于 Web 的程序,安全分析师只需像普通用户一样进行交互即可。未来正在考虑向 LYNX 引入自动输入格式推理组件,以简化输入生成过程。其次,与许多其他动态分析工具一样,LYNX 可能存在漏报。例如,使用的测试输入可能没有探索某些测试程序的所有分支。为了提高覆盖率,可以将 LYNX 与模糊测试技术结合起来。第三,Lynx 没有覆盖 Node.js 生态系统中存在的所有输入通道:在生态系统中,不同的程序可能使用不同的方法/代码实现来共享对象,因此很难系统地覆盖所有通道,也不是本文的重点。虽然知道 Lynx 并未涵盖所有输入行,但它确实涵盖了两种最流行的方法,并且可以支持大量程序。

 

0x06 Conclusion

在本文中对 Node.js 程序的对象共享进行了首次系统研究,并设计了一种名为隐藏属性滥用的新攻击。通过将以前无法访问的程序状态暴露给攻击者,新的攻击扩大了 Node.js 的攻击面。新的攻击面导致发现 15 个0 day漏洞,所有这些漏洞都可以被利用来引入严重的攻击效果。为了检测 HPA构建了 LYNX,这是一种新颖的漏洞发现和验证工具,它结合了静态和动态分析技术来查明和利用 Node.js 程序中存在漏洞的内部对象。对 102 个广泛使用的 Node.js 程序使用 LYNX,表明 LYNX 可以有效地检测 HPA 漏洞。

(完)