随着互联网技术的快速发展,Web应用程序成为基于互联网技术提供在线服务的最佳解决方案。大量敏感高价值信息存储在Web应用程序中,使得Web站点安全面临严峻的挑战。2022年进行的一项调查
[1]发现平均每个Web站点有15个漏洞。目前Web服务器端使用最广泛的开发语言为PHP,约占76.8%
[2]。但是PHP语言的脆弱性,使有效发现Web漏洞成为网站安全的重中之重。
在提高软件安全性方面,漏洞检测是基本的手段。静态分析方法是在不执行程序的情况下,通过抽象程序的控制逻辑和数据流来确定程序是否包含漏洞。传统的静态分析方法
[3-6]往往过度依赖于专家经验构建的漏洞模式定义。随着深度学习的兴起,深度学习算法可以提取代码漏洞特征,通过训练好的模型来检测漏洞,从而降低人工干预的主观性。近些年研究人员开始使用深度学习检测C语言和C++语言中的软件漏洞
[7-9],但是用于PHP语言漏洞发现的研究相对较少
[10-12]。现阶段,基于深度学习的Web漏洞检测方法仍存在不足之处。1)现有的方法在进行源代码分析、关系提取时花费的时间较多,尤其对于大型的Web应用程序和具有复杂结构的代码,表现尤为明显。2)无论是函数级别还是切片级别的漏洞检测,都一定程度忽略了程序源代码中函数名、变量名、文件名等包含的语义信息,这会导致准确率的下降。
在前人研究的基础上,观察到函数体中实现的操作可以通过函数原型和其所在的PHP文件名来推断。敏感数据库操作除了会出现在全局代码中,还常常包含在一些实现特定功能的自定义函数中。为了识别这些函数,提高漏洞检测效率,受到文献[
13]的启发,设计一种基于函数语义识别的PHP漏洞检测方法。该方法只关注源代码中的函数原型和所在PHP文件的名称(将其称为函数原型扩展)。首先,抽取Web应用程序源代码中的函数原型扩展,根据自建综合语料库切分为子词集合;其次,利用17个大型Web应用源代码的标记数据来训练一个由Transformer
[14]构建的孪生网络模型
[15],并利用该模型对PHP函数进行识别,将函数识别为包含敏感数据库操作的函数和不包含的函数。最后,利用静态源代码分析工具对全局代码和敏感数据库操作函数进行SQL漏洞检测。
1 相关研究
自20世纪70年代,静态检测技术就被应用于源代码审计工作。接下来重点介绍PHP漏洞检测技术,将现有的静态检测方法分为3类:传统检测方法、基于数据挖掘的检测方法和基于深度学习的检测方法。
1)传统检测方法。传统的方法主要是利用模式匹配、数据流分析来查找PHP代码中的漏洞。Cobra
[4]预先梳理相关敏感函数列表,提取抽象语法树,定位其中敏感函数,分析敏感函数的输入参数是否可控来识别OWASP TOP 10漏洞。文献[
5]设计了RIPS,通过对源代码文件进行标记和解析,构建控制流图,识别定位敏感函数,结合模拟每个基本块的数据流进行精确的污点分析。Pixy
[3]则是将流量敏感、过程间和上下文敏感技术相结合,来定位PHP应用程序中潜在的跨站点脚本攻击,文献[
6]将Pixy扩展到了SQL注入漏洞的检测。但是RIPS和Pixy无法用于面向对象编程的分析。phpSAFE
[16]解决了这个问题,phpSAFE配置一个敏感函数列表用于辅助漏洞检测,但是其漏洞检测效果受限于敏感函数列表能否及时更新。
2)基于数据挖掘的检测方法。数据挖掘常用污点分析、结合数据流的分析等方法,通过对漏洞特征进行建模、聚类和关联分析,完成漏洞识别。文献[
17-
18]主要对漏洞样本进行特征提取、特征缩放,利用决策树、支持向量机等算法实现漏洞的分类与识别。在文献[
17]的基础上,文献[
19]形成了一个WEAPONS框架,实现在线安全通知。文献[
16]针对Web应用程序插件进行训练与识别,通过收集用于训练和测试的Web应用程序插件,并提取其代码结构与特征数据,预处理后使用聚类、关联规则挖掘等方法对插件数据进行分析和挖掘,发现潜在的漏洞模式和异常行为。SanFinder
[20]则是通过提取代码属性图中程序的数据流路径,通过训练支持向量机分类器来识别PHP应用数据流中的自定义净化函数是否可靠,判断该数据流路径是否存在漏洞。
3)基于深度学习的检测方法。TAP
[10]使用基于标记和深度学习的方法检测PHP漏洞,能够识别超过7类漏洞,但是其在检测时只保留了带函数的代码行,这降低了对代码数据流和控制流的表征准确性。文献[
11]提出基于自然语言处理(Natural Language Processing, NLP)的深度学习模型,定义了逻辑扩展模块(Vulcan Logic Dumper, VLD)使用的所有有效操作码组成的词汇表,通过将操作码映射到词汇表的索引,为每个输入创建数字向量,作为神经网络的输入值。但是该方法会在一定程度上简化代码,并且忽略了代码中包含的语义信息。VulHunter
[12]利用字节码来表征漏洞,采用基于图的静态分析方法提取字节码片,利用神经网络模型计算目标程序与漏洞模板的相似度实现漏洞检测。基于深度学习的漏洞检测减少了人为偏差的引入,但是将程序表示成一维序列很难学习程序中的结构和上下文信息。
2 模型总体框架
函数语义识别的PHP漏洞检测方法如
图1所示,由数据预处理模块、函数识别模块和漏洞检测模块这3个模块组成,通过函数原型扩展推断函数功能,并进一步分析相应的代码实现是否安全。系统的输入是待检测Web应用程序的源代码,输出是潜在的漏洞点。
2.1 数据预处理
为了学习每个函数的语义,分析函数原型扩展,即函数原型和PHP文件名,而不是分析函数实现的整个代码片段。首先进行信息抽取,从函数源码中提取函数原型扩展,其次对抽取的信息进行基于语义的分词。NLP领域中最常见的分词方法是使用自然语言语料库,但是其对于编程语言的分词是不准确的,因为函数名和文件名通常会包含相关领域的专业术语、缩写和非正式惯用单词等,而这些在自然语言中是很少出现的。为了使分词结果能够最大程度表示语义信息,从StackOverflow论坛和GitHub上收集了包含编程语言上下文的帖子问答和项目的README文件,建立一个既包含自然语言预料,也包含丰富的程序语言的综合语料库。分3个步骤对抽取的函数原型扩展进行预处理,如
图2所示。
1)词表构建。利用NLP中的分词算法BPE(Byte Pair Encoder)算法
[21]将语料中的所有单词拆分为字符序列,并将字符进行迭代合并,将出现频率最高的相邻字符或子词合并成一个新的子词,并将该子词及其出现频率添加到词汇表中。
2)信息抽取。设计一个自适应性的PHP函数原型扩展抽取工具,使用正则表达式来识别并提取函数原型和文件名,并对其进行格式化,删除文件后缀、特殊符号等无效信息。
3)分词。使用步骤1)构建的词表对函数原型扩展的字符序列进行拆分
[22]。计算所有可能的拆分形式概率,如
式(1)所示:
式中:P(r)表示每种拆分形式出现的概率;P(w)表示每个子词s的出现频率。选择出现概率最高的拆分形式作为最终的分词序列,进行函数识别。
2.2 函数识别
虽然不同应用中函数原型和代码文件命名的习惯不同,但是通常会遵循一定的模式,人工总结这类模式低效且不精确。使用深度学习方法,通过海量数据学习函数原型和代码文件的特征模式进行相似性匹配,实现自动识别某些类型的函数(例如敏感数据库操作函数)。不同于Lyu等
[13]通过函数原型识别目标函数,本文还提取了文件名的语义信息进行辅助识别。
函数识别方法如
图3所示,基于Transformer模型的编码器设计孪生网络模型进行相似性检测以实现函数识别。该模型自动学习输入的函数原型扩展的特征,并对其特征向量进行匹配预测,识别该函数是否包含敏感数据库操作。
特征提取主要采用神经机器翻译中的Transformer模型作为函数原型扩展的编码器,自动捕获函数原型扩展的语义信息,生成函数原型扩展的嵌入向量。Transformer采用注意力机制,可以提取单词之间的依赖关系。该编码器的输入是函数原型扩展的分词序列,输出是一个上下文语义矩阵。由于上下文矩阵包含丰富的源序列语义信息,所以可以对上下文语义矩阵按列求均值得到一个维度固定的向量,作为函数原型扩展的嵌入向量
[23]。
在训练阶段,将读取的函数原型扩展正负样本对的分词序列分别送入孪生网络的两个编码器中,对其输出的特征向量进行计算得到均方差损失函数
[24],通过最小化均方差损失函数进行反向传播更新模型参数。
在预测阶段,计算待测分词序列嵌入向量和已知正样本分词序列嵌入向量的余弦相似度的均值,并以其作为判断标准。通过统计训练集正负样本的余弦相似度设定判断阈值,若余弦相似度的值大于阈值,则分类为真,即与正样本相似,将其识别并标记为包含敏感数据库操作的函数,反之不标记。余弦相似度的计算如式(2)所示:
式中, x 和 y 分别表示两个不同分词序列x和y的嵌入向量。
2.3 漏洞检测
DeepTective
[25]是一款文件级的混合图神经网络PHP漏洞检测工具,其在真实的Web应用程序上的漏洞检测表现出了优秀的准确性和扩展性,并且对大型源代码库的漏洞检测更加稳定。因此,使用DeepTective作为漏洞检测模块。
PHP语言作为一种脚本语言,具有较高的灵活性,这也就意味其规范性相较于C语言和C++语言要差一些。PHP代码除了在函数中定义的代码,还有大量的全局代码。因此,在确定函数类别后,对标记为包含敏感数据库操作的函数代码和全局代码进行漏洞分析扫描。
首先,从待检测的源代码文件中抽取函数原型扩展;其次,使用本文训练的模型对函数进行识别,将识别为敏感数据库操作函数所在的PHP文件和包含全局代码的PHP文件传递给漏洞检测器DeepTective,进行文件级的漏洞检测;最后,结合本文函数识别的结果定位潜在漏洞点,并生成相应的漏洞报告。
3 实验分析
3.1 实验环境
实验部署在1台64位Ubuntu 20.04操作系统的服务器上,语言环境为Python3.8.10,深度学习模型的训练环境为Tensorflow 2.4.0、CUDA 11.0.194、Gensim 4.3.1。使用的CPU为Intel Core i7-12700F,GPU为NVIDIA GeForce RTX 3070 Ti,内存大小为40 GB。
3.2 实验结果
3.2.1 函数识别有效性评估实验
构建数据集时,考虑到大型Web应用程序设计复杂,涉及的功能种类比较丰富,函数、文件名以及参数命名较为规范,在提取数据集时为了避免函数功能的单一化,从WordPress、Drupal等17个大型Web应用程序源代码中收集了32 609个函数样本,进行去重后作为数据集1,进行训练并验证。对其进行手工标注,其中包含敏感数据库操作的正样本和不包含的负样本比例大约为2:5。从数据集1之外的Jojo CMS、Pligg CMS、CodeIgniter、Cotonti、PHP-Fusion、GRR、vBulletin等10个Web应用程序的源代码中随机选取3 479个函数样本作为数据集2,用于测试,经手工标记,包含敏感数据库操作的函数有1 842个,不包含的有1 637个。
函数识别模型使用数据集1进行训练,使用Word2Vec模型实现子词嵌入,选取特征向量维度为40,使用Adam
[26]优化器,经多次实验验证,最终确定如
表1所示的训练参数。
使用数据集2用于测试评估模型在不同程序中函数识别的有效性,使用一些研究者广泛使用的衡量指标来验证函数识别的效果,分别为精确度(precision)、召回率(recall)、F1分数和准确率(accuracy)。PHP文件中路径信息、文件名、函数原型、注释等多种要素的语义信息在一定程度上可以体现函数功能,为选取函数识别的最佳依据,考虑到注释内容、长度、格式、标注位置缺乏通用规范,本实验仅关注函数原型、文件名、路径信息对函数识别的影响,实验过程分别以函数原型、函数原型扩展(即基于函数原型和文件名)、函数原型扩展和路径这3种形式的输入对模型进行训练和测试。
从
表2的实验数据可以看出,本文方法(以函数原型扩展为输入)在函数识别上的表现要优于其余两种方法,准确率达到83.24%,仅使用函数原型进行训练的模型效果最差。通过观察不难发现,大多数的PHP函数命名都与函数完成的功能相关,但是命名空间的引入,使得不同命名空间内相同函数名的出现成为可能。并且PHP是弱语言类型,不需要指明变量类型,函数名称和变量名称中包含的语义信息有限,对函数功能的反映也不足够准确。所以仅依靠函数原型进行函数语义识别是不够准确的,例如函数execute_Update,没有参数且函数名构成简单明了,仅从函数原型的语义信息无法判断其是否包含敏感数据库操作,需要更多的信息补充其语义特征。考虑到大多PHP文件名可以体现文件所属的功能模块或库的相关信息,函数名通常与文件名有一定的关联。因此,将文件名作为函数原型的补充信息提供更多的上下文,对提高函数语义的理解和识别是有效的。而引入路径信息后,各项指标下降,是因为路径中除了包含实现功能的信息,还包含大量开发者自定义的信息和与Web应用名称相关的信息,所以引入路径信息也意味着引入了大量噪声信息。
3.2.2 漏洞检测性能评估实验
考虑到在实际中Web应用程序的规模有大有小,为使实验结果更加客观准确,选取了大小不等的3个Web应用程序进行检测。其中函数识别模块使用函数识别有效性评估实验中的训练模型。具体来说,将3个Web应用程序Centreon v2.5.1、Exponent CMS v2.3.9和Piwigo v2.9.2的开源PHP代码作为实验样本。通过与美国国家通用漏洞数据库(National Vulnerability Database, NVD)已有的漏洞模式进行人工匹配,确认SQL漏洞所在的PHP文件和真实漏洞数。
实验首先从待测文件数量和漏洞文件数量两个方面,对以源代码为待测对象(基准实验)和本文方法筛选后代码为待测对象两种情况进行对比,结果如
表3所示。对于3个Web应用使用本文函数识别方法筛选后,检测范围大大缩小,缩小幅度均在50%以上,同时其漏洞文件覆盖率均在80%以上。这表明对本文方法能够将不包含敏感数据库操作函数的PHP文件有效剔除,在大大缩减漏洞检测范围的同时,较好地保留了包含漏洞的文件,对后续漏洞检测性能提升有效。
然后,使用DeepTective来检测Web应用的整个源代码作为基准,与本文系统进行漏洞检测时间开销对比。考虑到模型训练是一次性的,不同于每次进行漏洞检测都要执行的程序分析过程,因此训练时间不作为时间开销的评估因素。实验的对比结果如
表4所示。
从
表4的实验结果可以看出,本文方法首先进行函数识别,减少了最终漏洞检测的PHP文件总数,总的时间开销明显低于基准方法,检测效率大幅提升,其中3款检测效率提高超过40%。对于Centreon框架时间开销降低约23.89%,相较于其他Web应用程序检测效率提升程度较低。考虑到该程序中平均单个PHP文件包含的函数较多,函数提取识别时间花费较大,并且包含全局代码的文件数量占比较大,使得最终参与漏洞检测的PHP文件较多,检测效率提升有限。从两种方法检测出的真实漏洞来看,函数识别对DeepTective的漏洞检测范围进行了大幅缩减,但对最终真实漏洞的检出效果影响不大。其中Piwigo使用本文方法检测出的漏洞数比基准方法少,经确认是由于Deeptective漏洞检测的不稳定性导致,经函数识别筛选后的PHP文件中包含该漏洞所在的Qsearch.php文件,但是Deeptective未能将其检测出(在基准方法中成功将该漏洞检测出)。对于Exponent CMS,本文方法检测出的漏洞数比基准方法少2个,是由于函数识别准确率的限制导致。
3.2.3 与相关工具进行对比实验
为了验证本文漏洞检测系统效果优于现有工具,选取RIPS和Cobra这两款开源的PHP静态代码审计工具进行对比。选择4个实际的框架代码进行实验,包括靶场DVWA v1.9、Pikachu v1.0、开源的Web应用框架WordPress v3.3、phpBB v2.0.21。不同方法的漏洞检测数量对比结果如
表5所示。
从
表5可以看出,本文系统识别的漏洞数量多于RIPS和Cobra两款工具。其中RIPS是早期基于敏感函数污点分析的静态检测工具,适合进行SQL注入漏洞检测,但是RIPS在进行数据流构建时不够完整,导致漏洞检测的漏报和误报较多。而Cobra主要依靠函数黑名单和正则匹配漏洞规则进行漏洞检测,没有进行任何抽象语法树数据流图的提取,因此检测结果并不理想。而本文方法首先使用函数语义识别方法识别敏感数据库操作函数,其次对敏感数据库操作函数和全局代码所在文件进行漏洞检测,不仅缩小了漏洞检测范围,检测效果也比现有开源工具好。同时在实验过程中发现,在对大型Web程序进行漏洞检测时,RIPS和Cobra的稳定性不如本文方法。
总体而言,得益于函数语义识别,本文设计的PHP漏洞检测系统在对真实Web应用程序进行代码分析和漏洞检测时,既能大大提升检测效率,同时仍然能够确保准确地检测和报告PHP漏洞。
4 结束语
提出一种函数语义识别方法,该方法只需要少量的函数信息,即源代码中的函数原型扩展,即可预测函数功能分类。基于该方法设计一款针对PHP脚本语言的SQL漏洞检测系统,该系统通过分析部分源代码,即可实现更有效的漏洞检测。首先,通过函数原型扩展的语义相似度分析推断函数功能,识别包含敏感数据库操作的函数;其次,利用静态代码分析工具对识别后的函数和全局代码进行SQL漏洞检测。实验结果表明,本文方法能够有效识别包含敏感数据库操作的函数,同时能够实现更加高效的漏洞检测。下一步,将考虑使用本文方法识别其他类别的函数,从而有效检测相关漏洞类型。