代码分析平台CodeQL学习手记(三)

fanyeee 技术 2019年12月27日发布
Favorite收藏

导语:在上一篇文章中,我们为读者介绍了CodeQL平台相关的基本概念,并演示了如何编写和运行简单的QL程序。在本文中,我们将为读者介绍项目数据库的创建过程,以及如何借助数据库分析项目代码。

代码分析平台CodeQL入门(一)

代码分析平台CodeQL学习手记(二)

上一篇文章中,我们为读者介绍了CodeQL平台相关的基本概念,并演示了如何编写和运行简单的QL程序。在本文中,我们将为读者介绍项目数据库的创建过程,以及如何借助数据库分析项目代码。

数据库概述

在前面的文章中,我们已经编写过一些非常简单的QL代码,也就是通常所说的查询,接下来,我们将介绍如何借助于CodeQL库来分析项目中的代码。实际上,在之前的查询过程中,LGTM已经自动生成一个描述项目代码的数据库,因此,我们就可以直接通过QL代码来查询这个数据库了。接下来,我们就一起了解一下这个数据库的创建过程。

数据库的创建

实际上,在每次向存储库(repository)提交(commit)修改时,LGTM都会为其生成一个数据库。下面,我们先来解释这句话中的三个关键词。这里的“存储库”,可以简单粗暴地理解为一个存放项目中的文件夹、文件、图像、视频、电子表和数据集等资源的地方。而所谓的“提交”,就是保存对所做的修改;并且每次提交修改时,都可以添加相应的描述信息,以指出这次做了哪些修改,以及为什么做这些改变,等等。另外,这里所说的“数据库”,其实就是一个关系数据库,用于描述代码库在特定的瞬间,如进行某次修订或某次快照时的结构,其中包括代码的抽象语法树(AST),以及代码库的其他有用信息。

数据库的创建分为两步:

· 数据的提取:将源代码文件转换为代码所定义的底层层次结构。对于编译型语言来说,这一步需要编译源代码。

· 数据的导入:将提取的所有数据导入到数据库中,以便查询之用。

提取数据

LGTM首先会确定出哪些文件需要进行处理,然后,根据文件中的源代码所使用的编程语言来确定相应的“提取器(extractor)”,并将源文件转换为关系表示形式,即所谓的“陷阱(trap)”文件。

确定要分析的文件

对于像Java这样的编译型语言来说,在确定要分析的文件时,LGTM是通过运行和监视构建过程来完成的——监听系统调用,并检测对编译器的调用情况。然后,利用相应的提取器处理构建过程中用到的所有源文件。

对于像Python和JavaScript这样的解释型语言来说,可以直接使用提取器来处理源代码树中的文件,解析它们的依赖关系,以给出准确的代码库表示形式。

提取数据

提取器会将上一步确定出来的每个源文件都转换为一种关系表示形式,即“陷阱”文件。例如,对于Java源代码来说,提取器会为每个.java文件生成一个.trap文件,就像编译器会为每个.java文件生成一个.class文件一样。

1.png

导入数据

与把.class文件打包成.jar文件类似,当数据收集的工作完成后,提取器也会把上一步中生成的所有.trap文件导入到描述“整个”程序的数据库中。完成数据的导入工作后,提取器会创建一个“快照”。需要注意的是,这个快照由两部分组成,即数据库和所分析的所有源文件的副本。这样一来,LGTM就能够直接通过源代码文件来展示查询结果了。

该处理方式的优点

这种处理方式有两个优点,一个是准确,二是简洁。

为什么准确性高呢?因为对于每种编程语言(如果支持的话),都提供了一个单独的提取器,而非使用一个通用的提取器,从而确保了分析尽可能的准确——这是因为:

· 不同的编程语言之间可能存在显著的差别,因此,需要使用截然不同的处理方法。

· 适应不同编程语言之间的细微差异——例如,同样是继承的概念,在c++和Java之间却存在一定的差异。

同时,每种语言都使用自己特有的数据库模式。每种模式都规定了相应的方法表、表达式表,等等——每种语言结构都对应于一个表。在这里,我们稍微解释一下数据库模式,它是一个比较抽象的数据库概念,为了便于理解,可以做一个简单的类比——如果将数据库比作一个仓库的话,那么,模式就是仓库中的一个货架,并且每个货架分为多层,每层就对应于数据库中的一个表。

此外,由于提取器拥有编译器可用的全部信息,比如类路径等,所以,它能够将符号与其定义精确地绑定在一起。这种准确性对于跨编译单元边界的深度非本地分析而言是非常关键的。例如,通过传播受污染的数据来挖掘XSS之类的安全漏洞时,这种准确性就是至关重要的。

为了便于读者理解,我们这里举一个简单的例子。请考虑以下Java查询:当我们调用x.equals(y)时,变量x的类型(假设为T1类型)和变量y的类型(假设为T2类型)应该是兼容的。也就是说,类型T1和类型T2在继承层次结构中应该具有公共的子类型。

实际上,用于检查这种安全问题的查询是非常有效的,几乎可以在所有大型的Java代码库中都找到了真实存在的漏洞。为了挖掘这种类型的安全漏洞,要求安全分析人员能够正确地理解继承的层次结构。当然,您也可以尝试部分地处理继承层次结构,但是这将要求您重新实现Java编译器。

上面介绍了这种处理方式的准确性,下面我们再来谈谈其简单性。

无论处理什么样的数据,底层数据库平台和查询语言都是相同的。这里创建的数据库也是一种通用的关系数据库,只不过针对软件工程中的数据类型进行了相应的优化而已:

· 它提供了递归的有效实现,当我们需要深入了解层次结构和图结构(这里的图是只图论里讨论的图),就需要借助于递归操作。

· 由于针对原始数据编写查询是一件非常繁琐的事情(例如,C++语言的数据库模式规定了160多个表),因此,CodeQL平台提供了一种面向对象的机制,以便将视图从物理数据布局中抽象出来,并且是在不增加运行时成本的情况下实现抽象的。

此外,对于许多常用的操作,CodeQL已经提供了丰富的代码库加以支持,从而减轻了使用者的负担。这些内容,我们将在后面的文章中加以介绍。

通过查询数据库分析代码

在之前的示例代码中,我们只是简单介绍了一些基本数据类型的用法,这个过程中,虽然我们也选择了要分析的项目,可实际上并未用到这些项目所对应的数据库。下面的示例代码中,我们将切实用到这些数据库,并以便帮助大家进一步了解CodeQL的功能作用。

下面,我们通过具体的示例进行介绍。

查找符合要求的函数

首先,请打开查询控制台,并选择相应的语言和项目,具体如下图所示:

2.png

大家可能已经注意到了,选择语言的时候,我们一次只能选择一种语言;但是,选择项目的时候,则允许一次选择多个项目,也就是说,一次可以分析多个项目中的代码。然后,请输入下列代码:

from Function f
where count(f.getAnArg()) > 8
select f

首先,我们来看一下from语句,根据之前的经验,这里的变量f前面的Function应该是一种类型,但是这里比较陌生,也就是还不太确定,这时咋办?别急,只要将鼠标悬停在这个单词上面,片刻之后,查询控制台就会给出相应的提示信息,具体如下图所示:

3.png

从这里看,好像是一种函数类型,继续,按F3跳转到它的定义处:

4.png

实际上,这是一个类,对于不熟悉面向对象编程的读者来说,可以简单把它看作一种数据类型即可,只不过这些类型提供了相应的内建函数,专门处理这种类型的变量。按照面向编程的叫法,这些内建函数被称为成员函数。

现在,我们再来研究上面示例代码中的第二句。这个where语句中出现了一个陌生的函数,即getAnArg()——按照我们自己的话说,就是Function类型的一个内建函数(只要您喜欢,叫成员函数也行)。那么,这个函数的作用是什么呢?为此,我们可以将鼠标悬停在这个函数上面,查询控制台给出的提示是,这个函数是用于获取函数的位置参数的。结合count()函数,我们就可以断定这一句的含义是查找参数数目大于8的函数。然后,用select语句返回这些符合要求的函数。代码的运行结果如下所示:

5.png

如上图所示,我们在第一个项目中找到了643个符合要求的函数;在第二个项目中找到了330个符合要求的函数。现在,我们查看第一个项目中符合要求的函数,具体如下图所示:

6.png

如果我们点击返回的某个函数,查询控制台就会直接跳转到这些函数的定义处。

7.png

前面说过,快照含有源文件和数据库两部分。实际上,我们上述的代码查询的是数据库中的数据,然后,将符合要求的查询结果映射到对应的源代码,从而便于安全研究人员考察目标代码。

查找符合要求的参数

上面的例子介绍了如何查找项目中符合特定要求的函数,下面,我们再来看看如何查询符合特定要求的参数,比如,从未被使用过的参数,具体代码如下所示:

import java
 
from Parameter p
where not exists(p.getAnAccess())
select p

上面的代码中,第一句定义了一个表示函数参数的变量p,第二句规定了对要查找的参数的具体要求。其中,getAnAccess()函数表示被访问过一次。Exists()函数判断是否“存在”某种情况,所以,exists(p.getAnAccess())就表示参数p至少被访问过一次;而逻辑表达式not exists(p.getAnAccess())要想为真,exists(p.getAnAccess())的返回值必须为假,也就是说,数p至从来没有被访问过。最后,我们用select语句返回符合要求的参数p。

查找符合要求的注释

好了,下面我们为读者展示如何查找项目中符合要求的注释,具体代码如下所示:

import javascript
 
from Comment c
where c.getText().regexpMatch("(?si).*\\bTODO\\b.*")
select c

其中,我们在from语句中定义了一个变量c,用以表示注释。然后,我们在where语句中规定了对要查找的注释的要求,即注释的文本中必须包有单词“TODO”。其中,getText()用于获取注释文本,regexpMatch()是一个规则表达式匹配函数,其参数为一个规则表达式,这里为"(?si).*\\bTODO\\b.*",这个表达式的含义就是含有单词“TODO”。最后,我们用select语句返回符合要求的注释。

打开查询控制台,选择JavaScript语言,并选择下列项目:

8.png

运行上述代码后,我们可以打开一个返回的注释,看看里边到底有没有单词“TODO”:

9.png

10.png

如您所见,返回的注释中确实含有我们指定的单词,具体见上图中的红框处。

小结

在上一篇文章中,我们为读者介绍了CodeQL平台相关的基本概念,并演示了如何编写和运行简单的QL程序。在本文中,我们为读者介绍了项目数据库的基础知识,以及如何利用利用它来分析代码。

备注:本系列文章乃本人在学习CodeQL平台过程中所做的笔记,希望能够对大家有点滴帮助——若果真如此的话,本人将备感荣幸。

参考资料:https://help.semmle.com/

如若转载,请注明原文地址: https://beta.4hou.com/technology/22254.html
点赞 0
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论