代码分析平台CodeQL学习手记(九) - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

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

fanyeee 技术 2020-01-14 10:40:00
1031310
收藏

导语:在本文中,我们将为读者介绍如何利用查询控制台分析Python代码,以及用于分析Python代码的CodeQL库的知识点。

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

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

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

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

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

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

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

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

前面的文章中,我们介绍了QL语言的基础知识,接下来,我们将与读者一起学习如何利用CodeQL平台查找Python项目中的安全漏洞变种。在本文中,我们将为读者介绍如何利用查询控制台分析Python代码,以及用于分析Python代码的CodeQL库的知识点。

基于CodeQL的漏洞变种分析

前面说过,CodeQL是一种代码分析引擎,在它的帮助下,我们可以根据已知的安全漏洞,在其他源代码中查找相似的安全问题,这个过程就是我们常说的变种分析。通常情况下,安全工程师可以通过变种分析识别多个项目中潜在的安全漏洞,并及时给以修复。

实际上,CodeQL平台不仅能够对多个代码库(限于同一种语言编写的代码仓库)进行分析,同时也支持对多种编程语言编写的代码进行变种分析,例如C/C++、Java、Python等等。在前面的文章中,我们通过一些示例介绍了QL语言的数据类型、谓词、类以及递归等基础概念,接下来,我们将介绍如何利用CodeQL平台对特定的编程语言进行安全漏洞变种分析。

在本文中,我们将为读者介绍如何利用CodeQL平台查找Python代码中的漏洞变体。

利用查询控制台分析Python项目

对于特定的编程语言来说,我们可以同时分析一个或多个用该语言编写的代码项目。下面,我们将一个简单的CodeQL查询示例,来演示如何对Python项目进行分析。具体来说,这里做的事情,就是找出只包含pass语句的if语句——实际上,这些代码都是多余的,下面是一个具体的例子:

if error: pass

运行查询代码

首先,打开查询控制台,地址为https://lgtm.com/query。然后,从Language下拉列表中选择Python,然后从Project下拉列表中选择要查询的一个或多个项目。接着,将以下查询代码复制到查询控制台的文本框中:

import python
 
from If ifstmt, Stmt pass
where pass = ifstmt.getStmt(0)
  and pass instanceof Pass
select ifstmt, "This 'if' statement is redundant."

这时,LGTM会自动编译查询代码,并检查是否存在语法错误;如果一切正常的话,“Run”按钮将变成绿色的,表示查询代码已经通过了编译,可以运行了,具体如下图所示:

1.png

单击“Run”按钮,查询就会开始运行,运行结束后,原来的按钮会变成一个紫色的“View results”按钮。点击这个按钮,我们就可以看到查询代码返回的结果了:

2.png

如您所见,结果通常展示在项目名称下面。这里的查询结果分为两列,分别对应于查询的select子句中的两个表达式。第一列对应于表达式ifstmt,并链接到项目源代码中ifstmt所在的位置。第二列是警报消息。结果底部的省略号(…)表示有更多的结果可用,单击它就会显示更多的结果。

如果返回的结果数量不为0,单击ifstmt列中的相关链接,就会在代码查看器中显示符合要求的If语句。

3.png

我们可以看到,匹配的if语句在代码查看器中会以黄色背景突出显示。

查询代码的组成结构

现在,我们来解释一下这个查询中的组成部分。首先,是一个import语句,其作用是导入相应的标准库。在此之后,这个查询还包含三个语句,它们的作用与 SQL查询中的FROM、WHERE和SELECT子句的作用基本相同。

下面,我们进行逐行解读:

import python

这一行代码的作用是,导入用于分析python代码的标准查询库。注意,每个查询的开头部分都会有一个或多个import语句。

from If ifstmt, Stmt pass

这个from子句的作用是定义了两个变量,一个是变量叫做ifstmt,其类型为If,表示Python语言中的if语句;另一个变量是叫做pass,其类型为Stmt,表示一个语句。实际上,定义变量的一般形式为:


where pass = ifstmt.getStmt(0) and pass instanceof Pass

这个where子句定义了一个逻辑表达式。其中,pass = ifstmt.getStmt(0)表示pass是if语句体中的第一条语句;pass instanceof Pass的含义是变量pass中保存的是一个pass语句;而and是一个逻辑连接词,表示前后的判断必须同时成立,则整个判断才能成立。换句话说,这个表达式的含义是if语句体中的第一个语句就是一个pass语句。

select ifstmt, "This 'if' statement is redundant."

这里的select子句的作用是,规定要显示匹配项的哪些内容。就本例来说,就是显示符合要求的if语句,并给出一个解释性的文本,也就是上面的字符串。一般来说,用于查找不良编码实践实例的查询的select语句总是采用以下形式:

select

改进现有的查询

通常来说,查询的编写过程就是一个迭代的过程。例如,刚开始的时候,我们编写了一个简单的查询,通过运行,可能会发现以前没有考虑过的情况,或需要改进的地方,然后,着手进行修改,再次运行,如果有必要,继续进行修改,依此类推。

消除假阳性结果

如果仔细浏览上面查询返回的结果,就会发现我们的代码还有待改进,因为返回的结果中存在一些带有else分支的if语句,虽然这些语句的第一个分支为空,但是then分支确实是有实际用途的,所以,这些if语句根本就不是多余的。例如:

4.png

这些语句的的一般形式为:

if cond():
  pass
else:
  do_something()

像这样的结果,虽然then分支为空,但是else分支是有用的,这里将其判定为多余的if语句,就是通常所说的假阳性。为此,我们可以进一步修改查询代码:对于带有else分支的if语句,即使其then分支为空,也不将其归类为多余的if 。

为了排除带有else分支的if语句,首先需要修改where子句,并加入如下所示的约束条件:

and not exists(ifstmt.getOrelse())

上述代码的含义,就是if语句不能带有else分支。这样,我们的where子句就会变为:

where pass = ifstmt.getStmt(0)
  and pass instanceof Pass
  and not exists(ifstmt.getOrelse())

再次运行查询代码,我们会发现结果变得少了,因为带有else分支的if语句被排除掉了。

5.png

上面简单介绍了如何利用查询控制台分析Python项目,下面我们开始介绍用于分析Python代码的CodeQL库。

用于分析Python代码的CodeQL库

实际上,CodeQL平台专门提供了一个功能丰富的库,来帮助我们分析从Python项目中提取的CodeQL数据库。这个库中的类能够以面向对象的形式呈现数据库中的数据,并提供了许多抽象类和谓词来帮助我们完成常见的分析任务。这个库是通过一组QL模块(即扩展名为.qll的文件)的形式来实现的。其中,模块Python.qll的作用是导入这个库的所有核心模块,因此,我们可以在查询代码的开头部分通过下面的语句来导入完整的库:

import python

CodeQL用于分析Python代码的标准库为安全分析人员提供了丰富的类。对于库中的每个类来说,要么对应于Python源代码中的一种实体,要么对应于可以通过静态分析从源代码中派生出来的一种实体。总的来说,这些类可分为四种类型:

· 语法型:用于表示Python源代码中的实体的类。

· 控制流型:用于表示控制流图中的实体的类。

· 类型推断型:用于表示Python源代码中实体的推断值和类型的类。

· 污点跟踪型:用于表示实现污点跟踪查询的污点的源点、接收点和类型的类。

下面,我们将这些类型的类分别加以介绍,首先,我们来看看用于分析语法的类。

用于分析语法的类

这些类都是用于描述Python源代码的。其中,Module、Class和Function类分别对应于Python语言中的模块、类和函数,这些被统称为Scope类,也就是作用域类。同时,每个作用域类实际上就是一个语句列表,表中的每个语句可以由STMT类的子类来表示。当然,语句本身也可以包含其他语句和表达式,而这些表达式通常由expr类的子类进行表示。除此之外,还有一些其他的类,专门用于表示非常复杂的表达式(例如列表推导式,又称列表解析式)的各个组成部分。总的来说,这些类都是AstNode的子类,并对应于相应的抽象语法树(AST)。同时,每棵AST树的根节点都是一个模块。

此外,符号信息通常以变量(由类Variable表示)的形式附加到AST树上面。

作用域

Python程序通常都是由一组模块构成的。从技术层面上讲,模块就是一个语句列表,但我们通常把它看成是由类和函数组成的。这些源代码中的顶层实体,即模块、类和函数,对应于三个CodeQL类,即Module、Class 和Function类,它们都是类Scope的子类,其层次关系如下所示:

6.png

实际上,无论模块、类和函数,基本上都可以看作是一个由语句构成的列表,尽管它们还具有其他方面属性,如名称等。例如,下面的查询可以用来查找其作用域(声明它们的作用域)仍是函数的函数:

import python
 
from Function f
where f.getScope() instanceof Function
select f

我们点开了一个返回结果,如图所示:

7.png

语句

Python源代码中的语句通常是由Stmt类来表示的,该类具有大约20个子类,分别表示不同的语句,如Pass语句、Return语句或For语句等。我们知道,语句通常是由多个部分组成的,其中最常见的组成部分就是表达式,因此,这个标准库专门提供了一个Expr类来表示表达式。

下面,我们以Python语言的for语句为例,介绍表示语句的类的用法。先给出for语句示例:

for var in seq:
    pass
else:
    return 0

用于表示for语句的类是For,该类提供了许多成员谓词,用于访问for语言的各个组成部分,例如:

· getTarget(),返回表示变量var的Expr。

· getIter(),返回表示变量seq的Expr。

· getBody(),返回语句列表主体。

· getStmt(0),返回第一条语句,编号从0开始。就这里来说,就是pass语句。

· getOrElse(),返回包含return语句的StmtList。

表达式

众所周知,大多数语句都含有表达式,为此,这个标准库提供了许多表示表达式的类,并且它们都是从Expr类派生的,大概有30个类涉及调用、推导、元组、列表和算术运算。例如,我们可以使用BinaryExpr类来表示Python表达式a+2:

· 成员谓词getLeft()将返回表示“a”的Expr。

· 成员谓词getRight()将返回表示“2”的Expr。

如果我们想要查找项目中类似于a+2这样的表达式,即左边是一个简单的名称,右边是一个数字常量,我们可以使用如下所示的查询代码:

import python
 
from BinaryExpr bin
where bin.getLeft() instanceof Name and bin.getRight() instanceof Num
select bin

下图是展示的上述查询返回的一个结果:

8.png

变量

Python源代码中的变量可以使用CodeQL库中的Variable类来表示。该类具有两个子类,其中子类LocalVariable用于表示函数和类级别的变量,子类GlobalVariable用于表示模块级别的变量。

其他源代码元素

虽然程序的语义可以通过诸如Scope、Stmt和Expr等语法元素进行表示,但是源代码的某些部分仍然无法通过抽象语法树进行覆盖。例如,源代码中的注释,为此,该标准库提供了相应的类,其中Comment类就是用于处理源代码中的注释数据的。

我们知道,在处理Python项目时,CodeQL平台会将源代码中的所有语法元素都记录到相应的CodeQL数据库中。因此,我们可以通过相应的类来查询这些语法元素。下面,我们将通过几个简单的示例进行说明。

查找所有finally语句

在我们的第一个示例中,将通过Try类来查找项目中的所有finally语句,具体代码如下所示:

import python
 
from Try t
select t.getFinalbody()

查找无所事事的except语句块

在我们的第二个示例中,我们将项目中无所事事的except语句块,也就是说,其中除了pass语句之外,没有任何其他语句。为此,我们可以使用如下所示的查询代码:

not exists(Stmt s | s = ex.getAStmt() | not s instanceof Pass)

上述代码看起来有些复杂,下面我们进行简单的解读:其中,ex是ExceptStmt类的一个实例,也就是用来表示except语句;而Pass是表示pass语句的一个类。需要注意的是,这里使用了双重否定,也就是说exists(Stmt s | s = ex.getAStmt() | not s instanceof Pass)表示except语句中至少有一条语句不是pass语句的,但是,这个表达式前面又加了一个not,表示否定,所以上面这条语句的含义就变成:except语句中没有一句不是pass语句了。当然,我们也可以用肯定的形式加以描述,即所有的语句都是pass语句,这时,我们可以使用逻辑量词forall进行表示:

forall(Stmt s | s = ex.getAStmt() | s instanceof Pass)

当然,这两种表达形式是等价的。如果我们使用肯定的表述形式,相应的代码如下所示:

import python
 
from ExceptStmt ex
where forall(Stmt s | s = ex.getAStmt() | s instanceof Pass)
select ex

下面是上述查询返回的一个结果:

9.png

上面,我们介绍了表示语法时最常用的标准类,即Module、Class、 Function、Stmt以及 Expr类。接下来,我们开始介绍用于处理控制流的类。

用于分析控制流的类

这些类用于表示Scope类(即类、函数和模块)的控制流图。实际上,每个作用域类都包含一个由ControlFlowNode元素构成的图。此外,每个作用域都有一个入口点,以及至少一个(也可以有多个)出口点。为了提高分析控制流和数据流的速度,控制流节点通常会被分组为基本构造块。

例如,假设我们想要查找最长且没有任何分支的代码序列。根据定义,一个BasicBlock就是一个没有任何分支的代码序列,所以,我们只需要找到最长的BasicBlock即可。

首先,我们需要引入一个谓词bb_length(),以便将基本构造块与其长度关联起来。

int bb_length(BasicBlock b) {
    result = max(int i | exists(b.getNode(i))) + 1
}

由于BasicBlock中的各个ControlFlowNode都是从0开始连续编号的,因此,BasicBlock的长度等于该基本块中最大索引加1。

那么,我们如何利用这个谓词找出最长的BasicBlock呢?很明显,满足要求的基本构造块具有这样的特点:其长度与所有基本构造块中长度最长的那个相等——虽然听起来有些绕口,但是有利于翻译成QL代码呀:

import python
 
int bb_length(BasicBlock b) {
    result = max(int i | exists(b.getNode(i)) | i) + 1
}
 
from BasicBlock b
where bb_length(b) = max(bb_length(_))
select b

大家可能已经注意到了,bb_length(_)的参数是一个特殊的符号_,它表示这里无需关心具体的参数——可以是任意值,换句话说,就是表示所有的基本构造块。

好了,关于控制流的类就介绍到这里了,实际上主要有两个:

· ControlFlowNode:表示一个控制流节点。需要注意的是,抽象代码树节点和控制流节点之间存在一对多的关系。

· BasicBlock:表示一组没有分支的控制流节点。

用于类型推断的类

Python的CodeQL库还提供了这样一些类,它们被用于推断的值的类型,即类Value和ClassValue。通过这些类,我们可以分析表达式在运行时所属的类型。例如,可以使用以下查询代码来确定哪些ClassValue是可迭代的:

import python
 
from ClassValue cls
where cls.hasAttribute("__iter__")
select cls

关于这些类型的类,这里只做简单介绍,后面会撰文加以详述。

用于污点跟踪的类

用于分析Python代码的CodeQL标准库还提供了指定污点跟踪分析的类。我们可以覆盖Configuration类来指定污点的源点、接收点、清洗器和附加流程,以定义污点跟踪的分析过程。如果分析过程需要跟踪其他污点类型,我们还可以覆盖TaintKind类。

小结

在本文中,我们为读者介绍了如何利用查询控制台分析Python代码,以及用于分析Python代码的CodeQL库。在下一篇文章中,我们将为读者深入介绍一个重要的CodeQL标准类,即Function。

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

参考资料:

https://help.semmle.com/

如若转载,请注明原文地址
  • 分享至
取消

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

扫码支持

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

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务