PDGcross:基于跨文件图表征的源代码漏洞检测

2023-08-15 02:02熊可欣乔梦晴
计算机技术与发展 2023年8期
关键词:样例源代码调用

熊可欣,李 涛,余 琴,乔梦晴

(1.武汉科技大学 计算机科学与技术学院,湖北 武汉 430065;2.智能信息处理与实时工业系统湖北省重点实验室,湖北 武汉 430065)

0 引 言

随着软件的复杂性不断提高,漏洞的形态变得多样化,软件漏洞检测技术需求不断增加。为了实现漏洞检测的高准确率、低误报率,大量的深度学习方法被尝试用于检测源代码漏洞问题中,以实现漏洞检测的自动化和智能化[1-3]。在目前的漏洞检测中,重点关注于单个文件内部是否存在漏洞。然而在实际的项目开发中代码规模大,存在多种复杂的函数调用与参数传递,在单文件范围内有较好的检测效果,然而检测过程中都忽略了文件与文件之间可能存在的调用关系以及可能由于该类调用关系而产生的漏洞[4-5],多文件间的函数调用关系而产生的漏洞危害性较高但关注度较低,检测难度较高[6-8]。因此,该文将关注多文件间的函数调用,检测因调用关系而导致的漏洞。

在基于源代码的漏洞检测中,需要将源代码中的有效部分提取出并进行抽象表示,再进行向量化处理供给模型训练[9-11]。目前代码的特征表示方式可以大致分为文本、序列、图、抽象语法树和混合五种,不同的代码表征方式有其不同的优势[12-13]。

文献[14-16]将文本表示和深度学习相结合,从源代码中提取有效的特征信息,利用词频统计方法构建特征向量,采用深度学习对特征向量进行学习和训练。但普通的词法分析忽略了代码的上下文结构,无法确定代码内的函数调用关系。

序列表征是在源代码基础上提取字符流相关的标识符、函数名等关键特征信息,同时也包含一定的函数调用和语句调用等信息。在文献[17-20]通过序列表征进行漏洞检测,从函数调用序列出发,采用深度学习自动获取序列特征。文献[18]利用双向长短期记忆循环神经网络构建了一个VulDeePecker漏洞检测系统,文献[19]在文献[18]的基础上添加了控制依赖关系,但对全局特征和局部特征存在一定的学习偏差,文献[18-19]误报率都较大。文献[20]在程序执行过程中进行收集函数调用序列作为特征用来训练模型,可以更好地挖掘出更多的特征信息。

抽象语法树是源代码抽象语法结构的树状表现形式,树的每个节点中都包含源代码的语义和结构信息。文献[21]在抽象语法树的基础上进行项目内的漏洞预测。基于抽象语法树的检测可以在节点上进行标记,可以对漏洞的定位工作有一定的帮助。然而对于多文件之间的函数调用,抽象语法树的生成时间和空间复杂度都较高,难以适用于规模较大的系统。

基于图的表征方式可以通过图结构表示源代码的语义和词法,能够有效保留代码的上下文结构信息。相比于抽象语法树的表征方式,图表征中包含了更多的源代码内部结构信息。文献[22]提出获取Sink函数调用的代码子图,Sink函数即在源代码中可能导致漏洞的调用函数,但该检测仅局限于Sink函数内部有无漏洞,无法检测无漏洞文件间的函数调用导致的漏洞。文献[23]在代码属性图的基础上,学习图的局部和全局信息,利用程序切片技术简化图结构。

该文的漏洞检测重点关注多个无漏洞文件间因函数调用和参数传递而导致的漏洞,因此在将JAVA源代码转化为程序依赖图(PDG),保留源代码的上下文结构关系,提出了一种通过图节点信息观察函数调用关系、融合多文件的图特征为PDGcross特征、再进行深度学习的模型训练和预测的漏洞检测方法,实现了检测文件间函数调用而产生的漏洞。与传统的漏洞检测相比,该文的创新点在于更关注文件之间的函数调用关系,实现检测因函数调用和参数传递而导致的该类漏洞的高准确率和低漏报率。

1 基于PDGcross的源代码漏洞检测

1.1 整体架构

提出的基于PDGcross特征和LSTM模型的漏洞检测方法的整体架构如图1所示。

图1 基于PDGcross的源代码漏洞检测整体架构

首先,将源代码通过开源工具Sourcedg[24]转为程序依赖图(PDG);在程序依赖图的基础上遍历图节点,判断代码中有无跨文件的函数调用事件,若无则不做处理,若有函数调用事件则需要确定被调用文件,形成一个代码群;在当前文件所拥有的代码群中,通过数据流分析和控制流分析,融合被调用文件的节点,通过节点间数据依赖关系和控制依赖关系添加边信息,为当前文件形成一个新的PDGcross图表征,在PDGcross的基础上利用图嵌入将图表征转为特征矩阵,训练LSTM模型实现跨文件的漏洞检测。

1.2 基于程序依赖图的特征提取

在目前的图表征中,常用的图表征包含数据流图、控制流图、程序依赖图(Program Dependence Graph,PDG)、数据依赖图和数据属性图等,不同的图表征结构包含的源代码信息各有不同。程序依赖图主要包括控制依赖图(Control Dependence Graph,CDG)和数据依赖图(Data Dependence Graph,DDG),是源代码的一种图形表示,是带有标签的有向图,节点代表语句,边表示两种依赖关系。由于程序依赖图(PDG)通过在节点标签中保留代码信息,用图的有向边保留程序之间的上下文关系,可以更多地保留程序的控制依赖和数据依赖关系,因此该文选用了程序依赖图(PDG)的表征方式。

在Sourcedg通过JAVA源代码生成程序依赖图(PDG)的过程中,为了更好地表达节点信息,去除冗余信息,保留上下文关系,将程序依赖图的节点和边划分为多种类型,通过不同类型之间的关系进行图的绘制,有选择地保留节点和边。Sourcedg生成程序依赖图的过程中更多地关注于类型为类声明、实参的传入传出、形参的传入传出、方法入口和赋值等操作节点,对于其他类型的节点则较多的省略。在程序依赖图的边中,分为实际控制边和非实际控制边,实际控制边是指上下文中确实存在着控制关系,用实线边表示;为了更好地表达数据流和控制流,PDG中会产生一些与源代码无关的仅表示图结构的节点,在该类节点的关系中,这类则为非实际控制边,常用虚线边表示。

尽管Sourcedg工具生成的程序依赖图已经很大程度地保留了原代码的有价值内容,尽可能通过控制流和数据流表达了上下文关系。但当源文件中存在调用其他文件的操作时,在源文件的程序依赖图中并没有任何表达,仅将该类调用语句作为一个普通节点,不考虑调用产生的数据流和控制流。对于跨文件的函数调用,Sourcedg无法发现文件之间及函数之间的关系,仅仅依靠PDG的表征方法对多文件的函数调用产生的漏洞检测效率低下。

1.3 基于PDGcross的特征提取

以图2所示的代码为例,源文件A调用BC源文件,ABC源文件在单个检测时并无漏洞,但由于A调用了BC,导致在参数传递之后在A中形成了一个较为典型的SQL注入漏洞语句。在常见的漏洞检测方法中,一般在文件粒度上进行检测,即ABC分别检测,A中调用语句产生的数据流和控制流并不会被过多关注,仅仅是作为普通的语句进行检测。对于单个文件即造成漏洞的对于ABC该种文件间调用传参而造成的代码漏洞则被一定程度上忽略,可能变成漏洞攻击的薄弱之处。因此,该文的主要研究目的是要捕获ABC代码块之间的调用关系,通过观察调用关系而产生的数据流和控制流进一步进行漏洞检测。

图2 代码示例

在PDG的基础上进行节点的遍历,确定当前文件是否调用了其他文件,若有调用,则将被调用文件划分在当前文件的代码群中,存放在一个序列中。在构建代码群时的过程中,用字典的形式来存储任一单文件内的方法和声明方法的节点地址,即字典名{方法名:节点地址}。在产生调用关系之后,查找被调用文件中该方法的节点地址,最后存储为{调用节点地址:被调用地址}的格式,即为图融合中需要新增的边信息。在多文件中反复多次调用某一方法时,并不需要重复多次添加该方法相关节点,仅需添加相应的边来表达之间的调用关系。提取PDGcross特征的算法伪代码如算法1-3所示。

算法1:PDGcross生成算法

输入:输入样本的PDG特征集X={X1={nodes={n1,n2,…,nM},edges={e1,e2,…,eN}},…,Xq}

输出:输出样本的PDGcross特征O={O1={nodes={N1,N2,…,Nk},edges={E1,E2,…,ET}},…,Oq}

1.union←[] //初始化代码群列

2.entryDict←{}//初始化方法字典

3.dependeOther←{}//初始化依赖关系字典

4.forx←X1toXqdo//遍历PDG特征集

5.list←[]

6.调用算法2生成entryDict

7.调用算法3生成list和dependeOther

8.union.append(list)

9.end for

10.for files[]in union do//将属于同一代码群中的文件融合

11.if files[]长度>1 then//说明该文件存在调用关系

12.for file in files[]do//遍历文件代码群

13.融合该代码群中的PDG图节点和边

14.for nodes in dependeOther[file]do//遍历节点之间的依赖关系

15.添加相应的表示控制关系的边

16. end for

17. end for

18.end if

19.存为Oi

20.end for

21.returnO//输出结果

算法2:方法字典生成算法

输入:输入样本的PDG特征X={nodes={n1,n2,…,nM},edges={e1,e2,…,eN}}

输出:输出方法字典entryOne={{labelMeans1:node1},{labelMeans2:node2},…,{labelMeansM:nodeM}}

1.entryOne←{}//初始化方法字典

2.for node←n1tonMdo//遍历PDG的节点

3.labelType←node中的标签信息

4.labelMeans←node中的节点内容//包含方法名

5.if(labelType的类别==‘Entry’)(labelMeans not in entryOne)then//该节点为方法节点且该方法为被加入方法字典

6. entryOne[labelMeans]←node//键值对为(方法名:节点地址)

7.end if

8.end for

9.entryDict[]=entryOne

算法3:依赖关系生成算法

输入:输入样本的PDG特征X={nodes={n1,n2,…,nM},edges={e1,e2,…,eN}}

输出:输出依赖关系字典dependes

1.dependes←{}//初始化依赖关系字典

2.list.append(X)//把当前文件添加至代码群

3.for node←n1tonMdo//遍历PDG的节点

4.labelType←node中的标签信息

5.labelMeans←node中的节点内容//包含方法名

6.if labelMeans用正则表达式匹配到调用关系格式 then

7. filename←被调用文件名

8. method←被调用文件名

9. if filename是输入文件之一 then

10. if filename not in list then

11. filename调用算法2和算法3

12. end if

13. if filename in entryDict then

//依赖关系字典当前节点值列表中添加方法节点地址

14. dependes[node].append(entryDict[filename][method])

15. end if

16. end if

17.end if

18.end for

19.dependeOther[X]←dependes//赋值

1.4 特征矩阵

在图表征的基础上,采用了图嵌入算法将图表示为低维、实值、稠密的向量形式,以数值化的方式表达图中的信息,供给学习模型直接使用。图嵌入的方法主要分为矩阵分解、随机游走和深度学习,图基于随机游走技术的顶点嵌入经典算法包括DeepWalk、Node2Vec、SDNE等,该文则选取了其中的Node2Vec算法。Node2Vec算法在DeepWalk的基础上改进了随机游走的生成方式,采用有偏的随机游走方式获取顶点的近邻序列,使得生成的随机游走可以反映深度优先和广度优先两种采样的特性,再利用word2vec去学习顶点的embedding向量,最后得到45*64的特征矩阵。

1.5 模型训练

在PDGcross特征的基础上通过Node2Vec图嵌入得到特征矩阵后,按照文件是否含有漏洞为该文件对应的特征矩阵添加相应的标签,1为有漏洞,0为无漏洞,利用长短时记忆神经网络(LSTM)在含有标签的数据集上训练出分类模型,进行漏洞类别的预测。

在实验中,首先以CWE89类漏洞进行模型训练。模型的训练集负样本选择Juliet数据集CWE89类别中单文件即构成漏洞的数据集,包含352个样例;正样本随机选择benchmark数据集中无漏洞的350个样例。有漏洞文件的特征矩阵标签为1,无漏洞的则为0,数据集的90%划分为训练集,10%划分为测试集。基于LSTM神经网络,通过批标准化进行归一化处理,利用自适应调整学习率,快速又精确地获得最优模型。最终模型的学习效果十分不错,在测试集上的精确率和召回率都有很好的表现,最优时可以达到100%的准确率,从另一方面也可以证明图表征在模型检测方面有较好的效果。

2 实验结果与分析

为了验证所提特征提取的有效性,将其与原PDG图表征及现有的开源工具检测的结果进行对比实验。选用的数据集来自于NIST参考数据集SARD中的Juliet测试数据集JAVA语言版和OWASP组织下的OWASP Benchmark项目中的数据集。Juliet数据集包含了上百种CWE相关漏洞代码,Benchmark数据集包含了11类漏洞和无漏洞数据,该文选取了Juliet数据集中的三个子集与Benchmark数据集作为实验数据来源。

2.1 数据集

实验中选取了Juliet数据集中的CWE 15(External_Control_of_System_or_Configuration_Setting)、CWE 89(SQL_Injection)、CWE 90(LDAP_Injection)三类漏洞代码,见表1,benchmark数据集中无漏洞文件随机选取680个。

在Juliet数据集中,名为“****a.java”与“****b.java”的文件划分为一组,通过入口文件调用其他文件后组合产生一个漏洞,具有一个漏洞标签,称这样的一组为一个多文件样例。在Juliet数据集的一个多文件样例中,名为“****a.java”的文件为该组样例的入口,称其为该组样例的入口文件。

将Juliet多文件样例的入口文件又细分为AGS和G2BS两类,AGS类为整组样例中每单个文件并无漏洞,但因调用关系后产生漏洞,G2BS类为入口文件无漏洞但被调用文件有漏洞。实验重点关注于AGS类数据集的漏洞检测,针对G2BS类的数据可以作为安全风险提示。以CWE89的多文件样例为例,数据分布如表2所示。

表2 多文件样例构成

在人工检验后可以认证该类多文件样例确实包含相应的漏洞,然而在使用第三方检测工具Fortify和CodeSec扫描后,发现该类代码在检测工具中进行扫描时未发现该类漏洞,因此认为进行跨文件的漏洞检测十分具有必要性。

2.2 评价指标

在实验中,选取了precision、recall和F1值作为结果的衡量指标。precision为精确率,表示正确预测漏洞种类的样本数占全部预测为该类漏洞的样本数的比例;recall为召回率,表示正确预测漏洞种类的样本数占实际为该类漏洞的样本数的比例;F1值为和的调和平均数。

(1)

(2)

(3)

其中,TP表示预测为某类漏洞且分类准确的样本数量,FP为预测为某类漏洞但实际可能不存在漏洞或为其他漏洞的样本数量,FN为实际为某类漏洞但未被正确检测的样本数量。

2.3 实验结果

首先选取CWE89漏洞数据与benchmark无漏洞数据训练二分类模型,在训练得到CWE89漏洞的二分类模型后,抽取CWE89数据集中多文件样例的119组AGS类样例的入口文件和92组G2BS类样例的入口文件及单文件样例作为测试集进行漏洞分类。在实验中,特征提取阶段分别使用Fortify检测工具与PDG和PDGcross表征进行对比,检测结果如表3所示。

表3 模型检测结果

由实验可知,Fortify和PDG特征目前仅在单文件漏洞方面有不错的效果,但在多文件产生组合漏洞的样例检测中漏报率较高。而所提出的特征不论是在检测单文件漏洞、无漏洞文件因调用而产生漏洞(AGS类)的情况还是警告某一无漏洞文件调用有漏洞文件(G2BS类)的风险时都有较为不错的效果。因此在实验中进一步添加了CWE15和CWE90两类漏洞,训练了一个四分类模型,进行多文件样的检测,取得了91%的精确率和90%的召回率,优于现有的开源工具检测方法,结果如表4所示。

表4 四分类模型检测结果

3 结束语

聚焦于多文件间调用产生的漏洞检测,确定了被调用的文件范围,利用图表征技术中的程序依赖图实现了多文件的融合。采用LSTM神经网络,利用批标准化进行归一化处理,学习且训练出相应的漏洞分类模型。实验在CWE15、CWE89、CWE90这三种漏洞的小规模数据集上取得了91%的精确率和90%的召回率,在检测多文件调用漏洞方面更是优于CodeSec和Fortify等工具。但是目前漏洞检测工作是针对于文件粒度,缺少函数粒度的标签,无法更进一步的确定漏洞范围,因此下一步的工作是将代码粒度细化,减少特征中的冗余信息,做到一定程度的漏洞定位。

猜你喜欢
样例源代码调用
样例呈现方式对概念训练类别表征的影响
基于TXL的源代码插桩技术研究
“样例教学”在小学高年级数学中的应用
核电项目物项调用管理的应用研究
LabWindows/CVI下基于ActiveX技术的Excel调用
软件源代码非公知性司法鉴定方法探析
基于语法和语义结合的源代码精确搜索方法
基于系统调用的恶意软件检测技术研究
揭秘龙湖产品“源代码”
基于样例学习研究的几点展望