本论文相关内容
- 论文下载地址——Web Of Science
- 论文中文翻译——Automated Vulnerability Detection in Source Code Using Deep Representation Learning
- 论文阅读笔记——Automated Vulnerability Detection in Source Code Using Deep Representation Learning
文章目录
前言
此博客为Automated Vulnerability Detection in Source Code Using Deep Representation Learning论文的中文翻译,也是我的第二篇英文论文翻译,都是关于源码漏洞检测的英文论文。在翻译的过程中遇到了很多翻译不严谨的地方,我都加以我的理解进行了翻译。由于本人水平有限,可能有一些地方翻译的不是很完美,所以还请各位读者批评指正。
基于深度表示学习的源代码漏洞自动检测
作者信息
Jacob A. Harer,Onur Ozdemir,Paul M. Ellingwood,Marc W. McConley
Draper
Boston University
摘要
每年都会发现越来越多的软件漏洞,无论是公开报告还是在内部专有代码中发现。这些漏洞可能造成严重的开发风险,并导致系统危害、信息泄漏或拒绝服务。我们利用丰富的C和C++开源代码,利用机器学习开发了一个大型函数级漏洞检测系统。为了补充现有的标记漏洞数据集,我们编译了一个包含数百万开源函数的庞大数据集,并用三个不同静态分析器精心挑选的结果标记它,这些结果表明存在潜在漏洞。标记的数据集位于:https://osf.io/d45bw/。使用这些数据集,我们开发了一个基于深度特征表示学习的快速可扩展漏洞检测工具,该工具可以直接解释词法化的源代码。我们根据真实软件包和NIST SATE IV基准数据集的代码评估了我们的工具。我们的结果表明,对源代码进行深度特征表示学习是一种很有前途的自动化软件漏洞检测方法。
索引术语
人工神经网络、计算机安全、数据挖掘、机器学习
I. 引言
软件中的隐藏缺陷可能导致安全漏洞,使攻击者有可能危害系统和应用程序。每年有数千个此类漏洞被公开报告给常见漏洞和风险数据库,更多的漏洞是在内部专有代码中发现并修补的。最近的高调攻击表明,这些安全漏洞可能会在财务和社会方面产生灾难性影响。这些漏洞通常是由程序员犯下的细微错误引起的,由于开源软件和代码重用的盛行,这些漏洞可以迅速传播。
虽然存在用于程序分析的现有工具,但这些工具通常仅根据预定义规则检测有限的可能错误子集。随着开源存储库最近的广泛可用性,使用数据驱动技术来发现漏洞模式已经成为可能。我们介绍了机器学习(ML)技术,用于自动检测从真实世界的代码示例中学习的C/C++源代码中的漏洞。
II. 相关工作
目前存在各种各样的分析工具,试图发现软件中的常见漏洞。静态分析器,如Clang,无需执行程序即可执行。动态分析器在真实或虚拟处理器上重复执行带有许多测试输入的程序,以识别弱点。静态和动态分析器都是基于规则的工具,因此仅限于手工设计的规则,无法保证代码库的完整测试覆盖率。符号执行用符号值替换输入数据,并分析其在程序控制流图中的使用。虽然它可以探测所有可行的程序路径,但符号执行代价高昂,无法很好地扩展到大型程序。
除了这些传统工具之外,最近有大量关于将机器学习用于程序分析的工作。大量开源代码的可用性为直接从挖掘的数据中学习软件漏洞模式提供了机会。关于从“大代码”学习的全面回顾,包括与我们的工作没有直接关系的学习,请参见Allamanis等人。
在漏洞检测领域,Hovsepyan等人使用支持向量机(SVM)对一个简单的Java源代码标记化的单词包(BOW)表示进行预测,以预测静态分析器标签。然而,他们的工作仅限于对单个软件存储库进行训练和评估。Pang等人通过在SVM分类器使用的特征向量中包含n-gram扩展了这项工作。Mou等人通过嵌入源代码抽象语法树表示的节点,并针对简单的监督分类问题训练基于树的卷积神经网络,探索了程序分析的深度学习潜力。Li等人使用经过库/API函数调用相关代码片段训练的递归神经网络(RNN)来检测与这些调用的不当使用相关的两类漏洞。Harer等人训练RNN检测合成代码库中函数的词法表示中的漏洞,作为生成式对抗性代码修复方法的一部分。
据我们所知,在使用深度学习直接从大型自然代码库中的源代码学习特性以检测各种漏洞方面,还没有做过任何工作。以往大多数工作所使用的有限数据集(大小和种类)限制了结果的有效性,并阻碍了他们充分利用深度学习的优势。
III. 数据
考虑到程序的复杂性和多样性,需要大量的训练示例来训练机器学习模型,这些模型可以直接从代码中有效地学习安全漏洞的模式。我们选择在函数级别分析软件包,因为它是捕获子程序总体流的最低粒度级别。我们从SATE IV Juliet测试套件、Debian Linux发行版和GitHub上的公共Git存储库中编译了大量C和C++代码的函数级示例数据集。表I显示了我们从超过1200万个函数的数据集中的每个源收集和使用的函数数量的数据汇总。
SATE IV Juliet 测试套件包含118个不同的常见缺陷列表 (CWE)类漏洞的合成代码示例,最初设计用于探索静态和动态分析器的性能。虽然SATE IV数据集提供了许多类型漏洞的标记示例,但它由合成代码片段组成,这些代码片段不足以覆盖自然代码的空间,无法单独提供适当的培训集。为了提供大量的自然代码数据集来扩充SATE IV数据,我们从Debian包和公共Git存储库中挖掘了大量函数。Debian软件包发行版提供了一系列管理良好、经过精心策划的代码,这些代码目前在许多系统中使用。GitHub数据集提供了数量更大、种类更广(通常质量更低)的代码。由于Debian和GitHub的开源函数没有标签,我们使用了一套静态分析工具来生成标签。标签生成的详细信息见第III-C小节。
A. 源词法分析
为了从每个函数的原始源代码生成有用的特性,我们创建了一个定制的C/C++词法,旨在捕获关键标记的相关含义,同时保持表示的通用性,并最小化总的标记词汇表大小。使我们对来自不同软件存储库的代码的词法表示尽可能标准化,可以在整个数据集中传递学习。设计用于实际编译代码的标准词法捕获了太多的细节,这可能导致机器学习方法中的过拟合。
我们的词法能够将C/C++代码简化为表示形式,使用的词汇表总大小仅为156个标记。词汇表中包括所有基本C/C++关键字、运算符和分隔符。不影响编译的代码(如注释)被去除。字符串、字符和浮点字被词法化为类型特定的占位符标记,所有标识符也是如此。整数字是逐位标记化的,因为这些值通常与漏洞相关。常见库中可能与漏洞相关的类型和函数调用被映射到通用版本。例如,u32,uint32_t,UINT32,uint32,和DWORD都被词法化表示为32位无符号数据类型的相同通用标记。这些单个标记的学习嵌入可能会基于它们通常使用的代码类型来区分它们,因此要注意构建所需的不变性。
B. 数据管理
数据准备的一个非常重要的步骤是删除潜在的重复函数。开源存储库通常具有跨不同包复制的功能。这种重复会人为地夸大性能指标,并隐藏过拟合,因为训练数据可能会泄漏到测试集中。同样,有许多函数几乎是重复的,包含源代码中的微小更改,这些更改不会显著影响函数的执行。这些近乎重复的代码很难删除,因为它们通常会出现在非常不同的代码库中,并且在原始源代码级别上看起来也会有很大的不同。
为了防止这些问题,我们执行了一个极其严格的重复删除过程。我们删除了任何源代码的词法重复表示或编译级特征向量重复的函数。这个编译级特征向量是通过提取函数的控制流图、每个基本块中发生的操作(操作码向量或op-vec)以及变量的定义和使用(使用def矩阵)创建的。具有相同指令级行为或功能的两个函数可能同时具有相似的词法表示和高度相关的漏洞状态。
表I的“通过管理”行反映了重复删除过程后剩余的函数数量,约占删除功能总数的10.8%。尽管我们严格的重复删除过程过滤掉了大量的数据,但这种方法提供了最保守的性能结果,可以密切地估计我们的工具对以前从未见过的代码的性能。
C. 标签
在功能级别标记代码漏洞是一项重大挑战。我们的大部分数据集都是由挖掘出来的没有已知的真实情况开源代码组成的。为了生成标签,我们采用了三种方法:静态分析、动态分析和提交消息/错误报告标记。
虽然动态分析能够通过使用各种可能的输入来执行函数,从而暴露出细微的缺陷,但它非常耗费资源。对来自ManyBugs数据集的LibTIFF 3.8.2包的单个模块中的大约400个函数进行动态分析花费了将近一天的时间。因此,这种方法对于我们的超大数据集来说是不现实的。
事实证明,基于提交注释的标签非常具有挑战性,因为其提供了低质量的标签。在我们的测试中,人类和机器学习算法都不善于使用提交注释来预测相应的Travis CI构建失败或修复。受Zhou等人最近工作的启发,我们还尝试了一种简单的关键字搜索,寻找像“buggy”、“breakdown”、“error”、“fixed”等提交单词来标记之前和之后的函数对,在相关性方面取得了较好的结果。然而,这种方法大大减少了我们可以标记的候选函数的数量,并且仍然需要大量的手动检查,这使得它不适合我们庞大的数据集。
因此,我们决定使用三个开源静态分析器Clang、Cppcheck和Flawfinder来生成标签。每个静态分析器的搜索和检测范围各不相同。例如,Clang的范围非常广泛,但也会发现语法、编程风格和其他不太可能导致漏洞的发现。Flawfinder的范围面向CWE,不关注其他方面,如风格。因此,我们合并了多个静态分析器,并对其输出进行了删减,以排除通常与安全漏洞无关的发现,从而创建健壮的标签。
我们有一组安全研究人员将每个静态分析器的发现类别映射到相应的CWE,并确定哪些CWE可能会导致潜在的安全漏洞。这个过程允许我们根据CWE生成“易受攻击”和“不易受攻击的”二进制标签。例如,Clang的“越界数组访问”发现被映射到“CWE-805,长度值不正确的缓冲区访问”,这是一个可导致程序崩溃的漏洞,因此具有此发现的函数被标记为“易受攻击”。另一方面,Cppcheck的“未使用的结构成员”发现被映射到“CWE-563:未使用的变量赋值”,这是一种不太可能导致安全漏洞的糟糕代码实践,因此即使静态分析器标记了相应的函数,也会将其标记为“不易受攻击”。静态分析器的390种调查结果中,149种被确定为潜在的安全漏洞。大约6.8%的精选、挖掘的C/C++函数触发了一个与漏洞相关的发现。表II显示了这些“易受攻击的”函数中频繁发生的CWE的统计数据。Debian和GitHub的所有开源函数源代码以及相应的CWE标签都可以在这里找到:https://osf.io/d45bw/。
IV. 方法
我们的主要机器学习漏洞检测方法,如图I所示,将词法函数源代码的神经特征表示与强大的集成分类器随机森林(RF)相结合。
A. 神经网络分类与表示学习
由于源代码与编写有一些共同之处,并且为编程语言所做的工作更为有限,我们构建了为自然语言处理(NLP)开发的方法。我们利用与使用卷积神经网络(CNN)和递归神经网络(RNN)进行句子情感分类相似的特征提取方法进行功能级源脆弱性分类。
1)嵌入: 组成词法函数的标记首先嵌入到一个固定的 k k k维表示(限制在[- 1,1]范围内),该表示在分类训练期间通过反向传播学习到一个热点嵌入的线性转换。 在更大的未标记数据集上训练的几种无监督的word2vec方法被探索用于播种这种嵌入,但这些方法在分类性能上比随机初始化的学习嵌入的改进最小。还尝试了固定的一次热点嵌入,但效果不佳。由于我们的词汇量比自然语言小得多,所以我们能够使用比NLP应用程序中典型的小得多的嵌入。我们的实验发现, k k k=13对于有监督的嵌入大小表现最佳,平衡了嵌入的表达性和过度拟合。我们发现,加入少量随机高斯噪声 N ( μ = 0 , σ 2 = 0.01 ) \mathcal{N}\left(\mu=0, \sigma^{2}=0.01\right) N(μ=0,σ2=0.01)对每个嵌入表示都大大提高了对过拟合的抵抗力,比其他正则化技术(如权重衰减)更有效。
2)特征提取:我们使用CNNs和RNNs用于从嵌入式源表示中提取特征。卷积特征提取:我们使用形状为 m × k m×k m×k的 n n n个卷积滤波器,因此每个滤波器都跨越标记嵌入的整个空间。过滤器大小 m m m决定了一起考虑的顺序标记的数量,我们发现相当大的过滤器大小为 m = 9 m = 9 m=9时最有效。总共 n = 512 n = 512 n=512个过滤器,再加上通过ReLU进行批量标准化,效果最好。递归特征提取:我们还探索了使用递归神经网络进行特征提取,以允许捕获更长的标记依赖关系。嵌入表示被馈送到多层RNN,在长度为 l l l的序列中每个步骤的输出被级联。我们使用了隐藏状态大小为 n ′ = 256 n' = 256 n′=256的两层门控递归单元RNNs,尽管长短期存储器RNNs表现同样好。
3)池化:由于在外部发现的C/C++函数的长度可能会有很大的变化,卷积和递归特征都会沿着序列长度 l l l被最大池化,以生成固定大小(分别为 n n n或 n ′ n' n′)的表示。在此架构中,特征提取层应该学会识别不同的易受攻击的信号,因此,序列中的任何这些信号都很重要。
4)密集层:特征提取层后面是一个完全连接的分类器。训练时使用了到第一个隐藏层的最大池化特征表示连接的50%的信息丢失。我们发现,在最终softmax输出层之前使用64和16的两个隐藏层可以获得最佳的分类性能。
5)训练:为了方便数据批处理,我们只训练标记长度为 10 ≤ l ≤ 500 10 ≤ l ≤ 500 10≤l≤500的函数,填充到最大长度500。卷积网络和递归网络的训练均采用批次大小128的Adam优化(学习率分别为 5 × 1 0 − 4 5 × 10^{−4} 5×10−4和 1 × 1 0 − 4 1 × 10^{−4} 1×10−4),并且具有交叉熵损失。由于数据集非常不平衡,因此在损失函数中,易受攻击的函数权重更大。这个权重是我们为获得最佳性能而调整的众多超参数之一。我们使用SATEIV、Debian和GitHub组合数据集的80:10:10分割来训练、验证和测试我们的模型。我们根据最高的有效马修斯相关系数(MCC)调整和选择模型。
B. 神经表征的集成学习
虽然神经网络方法会自动构建它们自己的特征,但它们在我们的完整数据集上的分类性能并不理想。我们发现,使用神经特征(CNN中序列最大池卷积层的输出和RNN中序列的最大池输出状态)作为强大的集成分类器(如随机森林或极随机化树)的输入,在我们的完整数据集上产生最佳结果。分别优化特征和分类器似乎有助于防止过度拟合。这种方法还使得在新的特征集或特征组合上快速重新训练分类器更加方便。
V. 结果
为了提供一个强大的基准,我们在函数源代码的“单词包”(BOW)表示上训练了一个RF分类器,这忽略了标记的顺序。对词袋特征重要性的检查表明,分类器利用标签相关性与(1)源长度和复杂性指标和(2)调用的组合,这些调用通常被滥用并导致漏洞(如memcpy和malloc)。对这个基线的改进可以被解释为由于更复杂和具体的漏洞指示模式。
总体而言,我们的CNN模型在独立分类器和特征生成器方面表现优于RNN模型。此外,CNNs训练速度更快,所需参数更少。在我们的自然函数数据集上,基于神经特征表示训练的RF分类器在CNN和RNN特征方面均优于独立网络。同样,基于神经网络表示训练的RF分类器的性能优于基准BOW分类器。
图II显示了在我们的自然功能测试数据集上,所有主要机器学习方法的最佳版本的精确召回性能。精确召回曲线下的面积(PR AUC)和接收器操作特性(ROC AUC)以及验证最佳阈值下的MCC和 F 1 F_{1} F1得分如表III所示。图IV显示了我们最强的分类器在经过训练以从共享特征表示中检测特定漏洞类型时的性能。一些CWE类型比其他类型更具挑战性。
我们将我们的机器学习模型与SATE IV Juliet 套件数据集上收集的SA工具进行比较,该数据集具有真正的漏洞标签。图III显示了我们的模型在这个几乎标签平衡的数据集上的性能以及SA发现。我们发现,我们的模型,尤其是CNN,在SATE IV测试数据上的表现要比Debian和GitHub的自然函数好得多,这可能是因为SATE IV中包含的每个漏洞都有很多示例,并且具有相当一致的风格和结构。在SA工具中,Clang在SATE IV数据上性能最好,但与所有机器学习方法相比,仍然发现很少的漏洞。SATE IV的完整结果见表IV。
与传统的静态分析工具相比,我们的机器学习方法有一些额外的优势。我们的定制词法器和机器学习模型可以快速消化和评分大型存储库和源代码,而无需编译代码。此外,由于机器学习方法都是输出概率,因此可以调整阈值以达到期望的精确度和召回率。另一方面,静态分析器会返回固定数量的结果,对于庞大的代码库来说,这些结果可能非常大,对于关键应用程序来说可能太小。虽然静态分析器能够更好地定位它们发现的漏洞,但我们可以使用可视化技术,如图V所示的功能激活图,帮助理解我们的算法做出决策的原因。
VI. 结论
我们已经展示了使用机器学习直接从源代码中检测软件漏洞的潜力。为此,我们构建了一个广泛的C/C++源代码数据集,该数据集从Debian和GitHub存储库中挖掘出来,用一套静态分析工具中的精选漏洞发现进行标记,并将其与SATE IV数据集相结合。我们创建了一个定制的C/C++词法器来创建一个简单、通用的函数源代码表示,非常适合机器学习训练。我们在自然语言领域中应用了受分类问题启发的各种机器学习技术,并针对我们的应用对其进行了微调,并使用通过卷积神经网络学习的特征和使用集成树算法进行分类,获得了最佳的总体结果。
未来的工作应该集中在改进的标签上,例如来自动态分析工具或从安全补丁中挖掘的标签。这将允许机器学习模型产生的分数与静态分析工具更加互补。本文中开发的用于直接学习函数源代码的机器学习技术也可以应用于任何代码分类问题,例如检测样式冲突、提交分类或算法/任务分类。随着更大和更好标记的数据集的开发,源代码分析的深度学习对于更广泛的重要问题将变得更加实用。
致谢
作者感谢Hugh J.Enxing和Thomas Jost为创建数据获取管道所做的努力。该项目由空军研究实验室(AFRL)赞助,作为DARPA MUSE计划的一部分。
总结
到此为止,这篇论文的翻译工作也做完了,通过论文翻译真的可以学习到不少知识,但是还是有些专业性问题并不怎么理解。而且我发现国人写的英文论文读起来特别顺口,而外国人写的英文论文,翻译起来特别“不舒服”。看来以后要是用英文写论文还有很长一段路要走啊。下周我仍然会带来下一篇英文论文的翻译,还请读者关注!