模糊测试 - 论文翻译 - Montage: A Neural Network Language Model-Guided JavaScript Engine Fuzzer

本文前面约1000字非机翻,纯手工翻译 + 手工码字

原文地址:https://arxiv.org/pdf/2001.04107v2.pdf

蒙太奇:一个神经网络语言模型引导的JS引擎Fuzzer

摘要

Javascript(JS)引擎漏洞构成了影响数十亿web浏览器的严重的安全威胁。虽然模糊测试是很流行的寻找这类漏洞的技术,但是很少有研究使用最近神经网络语言模型(NNLM)的最近进展。在这篇文章中,我们提出了Montage,第一个NNLM指导的寻找JS引擎漏洞的模糊测试工具。我们的技术的关键方法是将JS抽象语法树(AST)转为可以直接供NNLM训练的AST子树序列。我们证明了Montage可以生成有效的JS测试文件,并且表现得比这方面寻找漏洞的先前研究好。Montage在最新版本的JS引擎中找到了37个现实世界的BUG,其中包括了3个CVE,证明了它在寻找JS引擎漏洞上的有效性。

1 简介

Web浏览器的内存安全已经变成了一个关键的攻击载体,因为它们已经成为了日常计算的一个重要组成部分。进行驱动下载攻击[48]的恶意网站通常利用浏览器的内存漏洞。目前,一个可被利用的浏览器内存漏洞可价值10万美元,如果它能与内核漏洞连接在一起实现远程iOS越狱,那么它将能够被卖到100万美元[59]。

在浏览器的众多组件中,攻击者最喜欢JS引擎,因为它的图灵完备性使得攻击者能够设计复杂的漏洞。一个人可以很轻松地分配一系列堆块来实现堆喷射[49](译者注:原文heap spraying,中文名词来自百度百科),在JS中编写一些函数来抽象出一些漏洞逻辑[26],甚至可以绕过浏览器的缓解措施[35]。根据国家漏洞数据库(NVD)的报告,微软Edge和谷歌Chrome在2017年报出来的所有漏洞中,有43%的是JS引擎漏洞。

尽管人们对JS引擎的安全性研究越来越重视,但是相比于寻找JS引擎的漏洞而言,有很少的学术研究是分析JS引擎脆弱性的[18, 24, 54]。LangFuzz[24]结合从JS种子文件中抽象出来的代码段生成JS测试文件,GramFuzz和IFuzzer[18, 54]或多或少地采用了类似的方法。但IFuzzer使用遗传基于执行目标JS引擎和产生输入所获得的反馈的遗传算法来进化指导模糊测试效果。

然后,现在的方法都没有考虑生成测试样例用的代码段之间的关系,换句话说,只要JS语法允许,它们就只通过简单地组合代码段来生成测试输入代码。因此,它们不知道那种组合更能暴漏JS引擎的漏洞。能够触发JS引擎漏洞的JS测试文件之间有相似性吗?如果有,那么我们可不可以利用这种模式来引导模糊器找到这些漏洞?这是激发我们研究的关键问题。

我们对JS引擎漏洞进行了初步的研究,并发现了两个模式。我们发现_一个新的安全问题经常出现在其他漏洞的补丁文件中_,我们分析了微软Edge使用的卡夫卡内核的50个CVE漏洞,发现分别有18%和14%的漏洞和GlobOpt.cpp和JavascriptArray.cpp相关。

我们的第二个发现是,_能触发JS引擎安全漏洞的JS文件经常由已经在回归测试套件中的代码片段组成_。我们从卡夫卡回归测试套件中收集了2038个不同的JS文件,并收集了67个能够触发漏洞的JS文件,这两组文件是互不相交的。我们将每个JS文件的抽象语法树切分成深度为1的子树(命名为代码段),发现67个文件生成的Fragments的95.9%和2038个文件的Fragments重叠(见第3部分)。

考虑到这两点,我们应该如何使用模糊测试来找到JS引擎的漏洞呢?对于这个待研究的问题,我们第一个在JS引擎上使用神经网络语言模型(NNLM)进行模糊测试的方法。我们的核心思想是使用NNLM生成的新代码段去替换给定的JS回归测试套件中JS文件的关键代码来实现变异。考虑到JS回归测试套件能触发调用漏洞补丁的函数,我们在这个回归测试中生成一个JS测试,同时期望在补丁中能引出一个新的潜在的漏洞,这就抓住了问题一。在组成新的代码时,我们还通过NNLM学习回归测试套件中的已有代码中,这就抓住了问题二。

为了验证这一思路,我们设计并实现了Montage,一个用于寻找JS引擎漏洞的系统。系统开始时将JS回归测试套件中的抽象语法树转化为代码段序列,并成为NNLM的训练集。这样,NNLM就学习了代码段之间的关系,Montage通过使用模型生成的代码段替换一个给定的JS测试文件的一个代码段从而实现变异。

先前研究主要集中在学习PDF组件[16],字符[11, 32],源码中的语法令牌[22, 40, 43]之间的关系,这些模型解决了完全不正确或缺失令牌[40, 53],或重组PDF对象[16]的问题,他们的方法不能直接用于生成有效的JS测试,因为这需要对结构控制流和JS词法标记之间的语义数据依赖进行建模。刘等人[32]指出他们在从C代码的字符级训练实例中提取一般模式方面的局限性,从而产生了虚假的测试。

和之前研究[11, 16]不同,Montage使用代码段序列作为构建块,每个代码段包含了抽象语法树节点中的关系。之后对模型进行训练,让模型能够学习抽象语法树代码段之间的关系,Montage在对给定的回归JS测试进行突变时使用该模型来组装单元子树。因此每个生成的JS测试都反映了回归测试套件中存在的语法和语义共性。

我们评估了蒙太奇在ChakraCore 1.4.1中发现的bug,并将发现的bug数量与CodeAlchemist[20]、jsfunfuzz[38]和IFuzzer[54]进行了比较。我们执行了5个fuzzing活动;每一轮72小时。蒙太奇发现了133个漏洞,其中包括15个安全漏洞。在发现的安全漏洞中,Montage分别报告了9个、12个和12个CodeAlchemist、jsfunfuzz和IFuzzer没有发现的漏洞。这个结果表明蒙太奇能够发现最先进的JS模糊器无法发现的错误。

我们衡量了蒙太奇语言模型与无语言模型的随机选择方法、马尔可夫链模型和字符/令牌级循环神经网络语言模型的有效性。蒙太奇在发现独特bug方面优于其他方法。

我们进一步测试了蒙太奇,以模糊最新版本的ChakraCore, JavaScriptCore, SpiderMonkey和V8。蒙太奇发现了37个独特的漏洞,其中包括3个安全漏洞。

从ChakraCore中发现了34个bug。剩下的两个和一个bug分别来自JavaScriptCore和V8。在这三个安全漏洞中,蒙太奇发现一个来自JavaScriptCore,另外两个来自ChakraCore。这些结果证明了利用nnlm查找真实的JS引擎错误的有效性。

2 背景

2.1 语言模型

语言模型是单词序列的概率分布。它对于自然语言处理(NLP)任务是必不可少的,例如语音识别、机器翻译和文本生成。传统上,语言模型估计一个单词序列在训练集中的出现历史的可能性。

一个n-gram语言模型[8,30]基于前面n−1个单词的出现历史来近似这种概率。不幸的是,这种基于计数的语言模型天生就存在数据稀疏问题[8],这导致它们产生糟糕的预测。问题的主要原因是缺乏具有代表性的培训实例。NNLMs通过将单词表示为分布式向量表示来解决数据稀疏性问题,这通常被称为单词嵌入,并将其用作神经网络的输入。

Bengio等[3]引入了第一个NNLM,一种前馈神经网络(FNN)模型。FNN根据它前面的n−1个单词预测下一个单词,这被称为历史或上下文,其中n是表示单词序列大小的超参数[1,3,17]。在这个NNLM设置中,训练集中的所有单词都构成词汇V。V中的每个单词都映射到一个特征向量上。因此,一个上下文,一个词序列,就变成了与其对应的每个特征向量的拼接。然后对模型进行训练,以输出给定上下文中下一个单词的V中的单词的条件概率分布。

**长短期记忆(LSTM)**。与FNN语言模型不同,循环神经网络(RNN)能够从任意长度的前一个单词的历史中预测下一个单词,因为RNN能够在长时间的单词历史中积累信息。LSTM模型是一种特殊的RNN;它被设计用来捕捉单词之间的长期依赖关系[14,23]。由于标准RNN存在梯度消失/爆炸问题[4],LSTM模型使用称为门的神经层来调节信息传播和内部内存,以在多个时间步长中更新其训练参数。

JS引擎模糊测试

模糊测试是动态软件测试的一种形式,在这种测试中,被测程序使用测试输入重复运行,以发现程序中的错误。根据输入生成方法的不同,模糊可以分为两种类型:突变模糊和代模糊。突变模糊[7,44,57,58]改变给定的种子以生成新的测试输入,而分代模糊[19,20,24,38]基于输入模型(如语法)生成测试。

由于JS代码是高度结构化的,随机生成的测试输入很可能会被JS引擎拒绝。因此,JS引擎模糊者通常采用分代方法。一个值得注意的例子是jsfunfuzz,一个开创性的JS引擎fuzzer[38,45]。它从在JS语法中定义的开始符号开始,并以随机方式选择下一个潜在的产品,直到没有剩余的非结束符号为止。CodeAlchemist[20]是另一代模糊器,它借助于称为代码块的构建块的组装约束来生成语义上有效的JS代码。

大多数其他JS引擎模糊器同时使用突变和分代方法。LangFuzz [24], GramFuzz[18]和IFuzzer[54]用JS语法解析JS种子,并构造一个代码片段池,其中一个代码片段是AST的子树。它们将池中的代码片段组合在一起以生成新的JS测试输入,但它们也会对给定的种子进行突变以生成测试输入。

尽管TreeFuzz[41]的目的不是寻找安全漏洞,但它利用概率上下文无关语法(PCFG)从给定的种子生成测试套件。类似地,Skyfire[56]从给定的种子推断出一个概率上下文敏感语法(PCSG),并使用它来生成一组分布良好的种子。这两种方法都应用概率语言模型来生成JS测试输入,但它们的设计过于通用,无法发现JS引擎中的安全漏洞。与以前的方法不同,Montage的灵感来自对cve的系统研究,即以前的JS引擎漏洞,并利用训练过的NNLM来学习JS回归测试套件之间的语法和语义共性。

3 动机

我们能在触发安全漏洞的JS文件中找到相似之处吗?我们通过对ChakraCore[10]报告的cve和相应的概念证明(PoC)漏洞进行定量分析来回答这个问题。我们之所以选择ChakraCore,是因为它的GitHub存储库维护了记录良好的提交日志,描述了提交是否修补了特定的CVE。这有助于我们确定哪些安全漏洞与给定的PoC利用有关,以及哪些源行受到该漏洞的影响。相比之下,其他JS引擎并没有在代码提交和CVE之间提供精确的映射。

请注意,收集PoC漏洞并不简单,因为CVE报告通常不携带任何PoC漏洞,因为存在被滥用的潜在风险。我们从exploitDB、漏洞博客和ChakraCore GitHub存储库中手动收集了cve及其PoC代码。我们总共获得了67个PoC漏洞,每个漏洞对应一个唯一的CVE。我们进一步确定了其中的50个,其中相应的漏洞通过一次提交就可以修复。这意味着我们可以将50个漏洞中的每一个映射到一组受影响的源文件。收集的漏洞中最早和最新的漏洞分别于2016年9月和2018年3月被修补。由于这些漏洞,总共有77个文件被修补。

我们发现50个漏洞中有9个(18%)与GlobOpt.cpp文件有关,该文件主要实现了即时(JIT)编译步骤。他们中的7人(14%)也为JavascriptArray.cpp文件的补丁做出了贡献。注意,每个文件实现了ChakraCore的不同功能。换句话说,不同的JS引擎漏洞通常产生于实现相同功能的公共文件,比如JIT优化和JS数组。例如,CVE-2018-0776的补丁在通过被调用方中的函数arguments属性访问数组时强制对数组进行深度复制,从而避免了类型混淆漏洞。然而,补丁是不完整的,仍然留下其他方式,在数组的浅拷贝可能导致。CVE-2018-0933和CVE-2018-0934被分配给这些bug。注意,所有的补丁都修改了JavascriptArray.cpp文件中的BoxStackInstance函数。

在77个补丁文件中,有26个(33.8%)文件由于报告的cve被至少打了两次补丁。这些例子表明,JS引擎的漏洞通常来自为其他错误修补的文件。考虑到这些补丁通常是通过回归测试来检查的,改变一个现有的JS测试可能会触发一个新的漏洞,其根本原因在于这个测试已经覆盖的修补文件。

发现1:JS引擎漏洞通常来自于针对不同错误修补的同一个文件。

我们还测量了来自PoC漏洞的JS代码和从ChakraCore维护的回归测试套件中获得的2038个JS文件之间的语法相似性。请注意,回归测试套件由触发先前修补的错误的JS测试组成,并使用对抗性测试输入检查预期结果。特别是,我们收集了2016年8月发布的ChakraCore版本的回归测试文件,这比最早的漏洞修补日期提前了一个月。因此,回归测试文件不受任何研究漏洞的影响。

1
2
3
4
var v0 = {};
for ( var v1 = 0; v1 < 5; v1 ++) {
v0 [ v1 ] = v1 + 5;
}

图1:规范化后的JS文件示例

为了度量相似性,我们规范化了回归测试文件中的标识符以及PoC漏洞。具体来说,我们重命名了变量和函数的每个标识符,使其具有一个连续的数字和一个公共前缀作为它们的名称。然后我们将规范化的JS文件解析为抽象语法树。

我们从每个AST中提取一组深度为1的单元子树。对于给定的AST,我们从每个内部节点中提取一个单元子树。因此,提取的单元子树的数量就成为AST内部节点的数量。我们称这样的单元子树为片段,如§5中正式定义的那样。注意,每个片段的根节点都是AST的内部节点。它也对应于另一个片段中的叶节点,除了具有原始AST根节点的片段。

图2:从图1中的例子中分割AST(此处有图!!Figure2)

图2说明了图1中列出的JS文件的碎片结果。图的上方显示了从Esprima JS解析器[21]获得的AST子树。这个子树对应于第3行。图的底部显示了这个子树的片段。

我们还将触发CVE的每个PoC划分为片段,然后计算回归测试套件中存在多少个片段。图3描述了公共片段百分比超过每个百分比阈值的PoC文件的数量。我们发现来自10个PoC漏洞的所有片段(100%)已经存在于回归测试文件中。42个PoC漏洞中96%以上的片段存在回归测试,63个PoC漏洞中90%以上的片段存在回归测试。平均而言,在回归测试文件中发现了95.9%的PoC漏洞片段。

发现2:在回归测试套件和PoC漏洞之间,超过95%的代码段在语法上重叠在回归测试和PoC漏洞之间,超过95%的代码段在语法上重叠。

这两个观察结果都表明,从现有的回归测试套件中组装代码片段很可能会触发一个新的安全漏洞,这是本研究的主要动机,如我们在§4中所述。

4 概况

我们展示了蒙太奇,一个NNLM驱动的模糊器,它可以自动发现JS引擎中的错误。回想一下蒙太奇的整体设计是由两个观察结果驱动的:(1)安全漏洞通常来自于先前因不同原因打过补丁的文件,以及(2)触发安全相关漏洞的JS测试代码大量重用了现有回归测试集中发现的AST片段。

我们提出了一种新的模糊技术来捕捉这些观察。我们训练一个NNLM来从回归测试集中捕获片段之间的语法和语义关系。当生成一个新的JS测试时,蒙太奇会改变给定JS回归测试的AST。它使用训练过的NNLM将AST的子树替换为新的子树。因此,每个生成的测试都源于一个给定的回归测试,该测试检查先前修补过的或有bug的逻辑,从而捕获第一个观察结果。同时,它通过在NNLM的指导下组装现有片段来调用不同执行上下文中的功能,NNLM解决了第二个问题。

图3:普通片段百分比大于可变百分比的所有PoC文件的数量。(图3!!!)

图4显示了蒙太奇的整体工作流程。阶段I从给定的回归测试套件准备训练实例。每个训练实例都是AST单元子树的序列,称为片段。阶段II训练NNLM学习片段之间的组成关系。这两个阶段是一次性的设置过程。阶段III通过利用训练好的模型生成JS测试。

图4:蒙太奇的概况(图4!!!)

阶段I从给定的JS回归测试文件训练集开始。它将每个JS文件解析为AST,并规范化AST中出现的标识符,以消除重复的函数和变量名。图1显示了一个规范化的JS文件示例。每个出现的变量名都被更改为一个通用名称,例如v0或v1。然后,阶段I从规范化的AST树中提取多个单元子树,每个子树称为一个片段。对于AST中的每个节点,蒙太奇递归地切片深度为1的单位子树。每个切片的子树都成为AST的一个片段。然后它发出这些片段的序列,这些片段是由规范化AST树中它们的根节点的预序遍历产生的。

阶段II给出一组片段序列训练NNLM。根据给定的任意长度的片段序列,我们设计了NNLM来建议可能出现在该片段序列之后的下一个片段。这个框架是本文的一个关键贡献。请注意,以语言模型可以学习的方式对AST的固有结构关系建模并不简单。通过利用封装AST结构关系的片段,我们将给定的AST编码到片段序列中。考虑到大量的自然语言nnlm已经在单词序列上进行了训练,这种片段测序简化了现有流行的nnlm用于生成JS测试的应用。

在这里,目标是训练NNLM学习片段之间的组成关系,以便从训练模型生成的JS测试代码反映给定训练集的语法和语义,这是JS引擎的回归测试集。阶段III通过利用训练好的模型和回归测试的AST生成一个新的JS测试。给定一组来自回归测试套件的AST,它随机选择一个种子AST。然后,它随机选择一个子树进行蒙太奇替换。在生成新的子树时,蒙太奇会考虑上下文,即在所选子树之前的所有片段的序列。蒙太奇迭代地从所选子树的根节点追加片段,同时考虑其上下文。

因为当前的AST是从片段组装而来的,所以可以预期AST节点中的一些变量和函数标识符在没有适当声明的情况下使用。因此,Montage通过使用声明的标识符重命名引用错误来解决可能的引用错误。最后,Montage检查生成的测试,并在代码使目标JS引擎崩溃时报告错误。

其他模型指导的方法。以往的研究提出了语言模型,可以预测源代码中的词法代码标记。这种语言模型的框架在解决代码完成问题时已被广泛研究[40,53]。然而,可执行测试的生成比预测有限数量语义正确的词法标记的代码完成问题更具挑战性。据我们所知,由Singh等人[16]提出的PDF模糊器是第一个使用字符级RNN模型来生成PDF测试的系统。我们评估了基于片段的方法在寻找JS引擎错误方面是否比字符级RNN模型方法表现更好(参见§7.5)。

5 设计

Montage的设计目标是生成能够触发JS引擎安全漏洞的JS测试输入,它(1)反映给定JS训练集的语法和语义模式,(2)不会触发引用错误。

用训练代码的语义和句法模式来构建语言模型是一项技术挑战。我们通过AST子树抽象层次结构来解决这个问题,我们将其称为片段。然后,我们使语言模型能够学习片段之间的组合关系。

我们提出了一种利用训练过的语言模型的新的代码生成算法。我们利用现有的JS代码来触发JS引擎缺陷。Montage通过将现有的JS代码中的一个AST子树替换为经过训练的语言模型生成的新子树来改变它。因此,Montage能够生成一个新的JS测试,语义上类似于触发先前报告的错误的回归测试用例。我们希望这个新的JS测试在不同的执行环境中触发一个新的错误。

5.1 第一阶段:构建片段序列训练数据

第一阶段使用给定的训练集准备训练实例。它由解析和分段组成。

5.1.1 解析和规范化

阶段I通过解析训练集中的每个JS文件来构建AST,并对解析后的AST进行规范化。由于训练集中包括来自不同开发人员的各种JS文件,标识符命名实践不一定一致。因此,训练文件在不同的JS文件中有不同的变量和函数名是很自然的。考虑两个JS文件,它们分别包含一个JS语句var b = a + 1和var c = d + 1。两者具有相同的AST结构和语义,但标识符不同。

这种模式增加了语言模型需要学习的不必要词汇量,使模型评估变得昂贵,因为它需要更多的训练实例。为了使ast具有一致的标识符名称,我们重命名ast中的所有变量和函数标识符。

具体来说,对于每个声明的变量标识符,我们按照它们在给定AST中出现的顺序分配一个连续的数字。然后,我们将每个变量名替换为一个结合了公共前缀和它的连续数字的新名称,例如v0和v1。我们还将相同的过程应用于函数标识符,例如f0和f1。我们故意将特定于语言的内置函数和引擎对象从归一化步骤中排除,因为归一化会影响原始AST的语义。对于一个作为JS代码动态计算给定字符串的eval函数,我们首先提取eval函数的参数字符串,当参数是常量字符串时,将其作为JS代码剥离。随后,我们将从eval参数中剥离出来的JS代码中的标识符规范化。

由于我们的训练集来自于JS引擎的回归测试,集中的JS文件大量使用预定义的函数进行测试。因此,我们手动识别这些供应商提供的测试函数,并在规范化步骤中忽略它们。也就是说,我们将每个JS引擎供应商提供的通用测试函数视为内置函数,并将它们排除在标准化之外。

5.1.2 分段化

原创不易,转载请附上原文链接哦~
原文链接:https://leetcode.letmefly.xyz/2023/04/06/Other-Fuzz-Montage-JSEngineFuzzer-Translation/


模糊测试 - 论文翻译 - Montage: A Neural Network Language Model-Guided JavaScript Engine Fuzzer
https://blog.letmefly.xyz/2023/04/06/Other-Fuzz-Montage-JSEngineFuzzer-Translation/
作者
Tisfy
发布于
2023年4月6日
许可协议