CodeQL(基础)

CodeQL 概览

CodeQL是一个静态代码分析引擎,用来给半自动化检查代码中可能存在的漏洞和bug。使用 CodeQL 进行代码审计包括下面三个步骤:

  1. 从源代码中提取必备信息,然后创建 codeql 数据库
  2. 用他人编写好 or 自定义的 ql 语句进行查询
  3. 解释分析查询结果

创建数据库

创建数据库通常使用固定的命令:

1
codeql database create /codeql-dbs/example-repo --language=<language-label> --source-root /checkouts/example-repo

当然不同语言存在差异,可以以编译型 / 非编译型进行区分,可以参考:为 CodeQL 分析准备代码

非编译型语言

非编译型语言构成的项目,其数据库的构建方式通常比较简单,指定源码文件夹和输出文件夹即可。

Javascript 和 typescript 项目

1
codeql database create --language=javascript-typescript --source-root <folder-to-extract> <output-folder>/javascript-database

Python 项目

1
codeql database create --language=python <output-folder>/python-database

Rubu 项目

1
codeql database create --language=ruby --source-root <folder-to-extract> <output-folder>/ruby-database

编译型语言

编译型语言的数据库构建相对复杂,因为项目需要先进行编译,然后生成最终的项目,而不同的项目可以使用不同的构建工具进行管理,所以这一块比较复杂。

codeql-cli 提供了两种思路,一是自动识别进行数据库生成, 二是显式指定生成最终项目的命令,既项目构建的命令。

例如,自动构建 C++ / C 项目的数据库的命令如下:

1
codeql database create --language=cpp <output-folder>/cpp-database

而显式指定构建项目的命令来生成 C / C++项目的数据库的命令如下,多一个参数而已:

1
codeql database create cpp-database --language=c-cpp --command=make

显式指定构建命令生成 C## 项目数据库:

1
codeql database create csharp-database --language=csharp --command='dotnet build /t:rebuild'

显式 指定环境变量 / 指定构建脚本 生成 Go 项目数据库:

1
2
3
CODEQL_EXTRACTOR_GO_BUILD_TRACING=on codeql database create go-database --language=go

codeql database create go-database --language=go --command='./scripts/build.sh'

显示指定 maven 生成 Java 项目数据库:

1
codeql database create java-database --language=java-kotlin --command='mvn clean install'

显示指定 Ant 生成 Java 项目数据库:

1
codeql database create java-database --language=java-kotlin --command='ant -f build.xml'

执行查询

创建完数据库之后,在 vscode 的插件里按文件夹导入,即可创建 ql 脚本,执行查询操作。

查询结果

类似 sql 查询,通过 运行 ql 语句进行查询操作,同时返回一个结果集,包含符合 ql 语句要求的代码片段。

CodeQL 快速入门

首先是环境搭建,官方文档推荐使用 vscode extension,本质就是下面三步走:

  • 下载 codeql-cli 和 packs 的捆绑包,并配置好环境变量
  • vscode 安装 CodeQL 插件,并在 vscode extension settings 里配置好 codeql-cli 的路径
  • 下载 vscode-codeql-starter workspace,双击 vscode-codeql-starter.code-workspace打开 workspace

游戏例子

官方文档里,通过游戏例子帮助入门: https://codeql.github.com/docs/writing-codeql-queries/ql-tutorials/

例-01:find the thief

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// See https://codeql.github.com/docs/writing-codeql-queries/ql-tutorials/#ql-tutorials.
import tutorial

from Person t
where t.getHeight() > 150
and t.getHairColor() != "blond"
and exists( string c | t.getHairColor() = c)
and not t.getAge() < 30
and t.getLocation() = "east"
and (t.getHairColor() = "brown" or t.getHairColor() = "black")
and not (t.getHeight() > 180 and t.getHeight() < 190)
and not forall(Person p | t.getAge() > p.getAge())
and t != max(Person p | | p order by p.getHeight())
and t.getHeight() < avg(Person p | |p.getHeight())
and t = max(Person p |p.getLocation()="east"| p order by p.getAge())
select t

例-02:Catch the fire starter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import tutorial

predicate isSouthern(Person p) {
p.getLocation() = "south"
}

predicate isBald(Person p) {
not exists(string c | p.getHairColor() = c )
}

class Southerner extends Person {
Southerner() { isSouthern(this) }
}

class Child extends Person{
Child(){this.getAge() < 10}

override predicate isAllowedIn(string region) {
region = this.getLocation()
}
}

from Southerner s
where s.isAllowedIn("north")
and isBald(s)
select s, s.getAge()

例-03:Crown the rightful heir

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import tutorial

Person childOf(Person p) {
p = parentOf(result)
}

Person ancestorOf(Person p){
result = parentOf(p) or
result = parentOf(ancestorOf(p))
}

Person relativeOf(Person p){
result = childOf*(ancestorOf(p)) and not result.isDeceased()
}

Person isATheif(Person t){
result = t
and t.getHeight() > 150
and t.getHairColor() != "blond"
and exists( string c | t.getHairColor() = c)
and not t.getAge() < 30
and t.getLocation() = "east"
and (t.getHairColor() = "brown" or t.getHairColor() = "black")
and not (t.getHeight() > 180 and t.getHeight() < 190)
and not forall(Person p | t.getAge() > p.getAge())
and t != max(Person p | | p order by p.getHeight())
and t.getHeight() < avg(Person p | |p.getHeight())
and t = max(Person p |p.getLocation()="east"| p order by p.getAge())
}

Person isAFireStarter(Person f){
result = f
and f.getLocation() = "south"
and not f.getAge() < 10
and not exists(string c | f.getHairColor() = c )
}

Person hasCriminalRecord(Person p){
result = isAFireStarter(p) or
result = isATheif(p)
}

from Person p
where p = relativeOf("King Basil")
and not p = hasCriminalRecord(p)
select p + " You are the next king!"

例-04:Cross the river,这个偏算法思想,不看。

实际例子

以 java-sec-code 项目为例,创建数据库命令的命令:

1
codeql database create /Users/jasper/Documents/Security/tools/CodeQL/databases/java-sec-code-database --language=java --source-root=/Users/jasper/Documents/Security/java/java-sec-code --command="mvn clean package"

点侧栏点CodeQL插件,选Java -> 导入上面创建的数据库 -> 运行example.ql,能正常运行输出结果说明环境正常。

再以 activemq 为例,注意 maven build 需要科学上网,源码是直接在 activmq GitHub clone 的。

1
codeql database create .\codeql-databases\activemq --language=java-kotlin --source-root .\activemq-activemq-6.1.5\ --command='mvn clean install -Dmaven.test.skip=true'

编写查询,查找 activemq 项目里所有调用了 .equals("") 的函数,并且调用 该 equals() 的对象的类型应为 String

注意:官方文档上 MethodAccess 已经弃用,MethodAccess 改名为 MehtodCall,MehtodCall.getMethod()获取到的是被调用的函数,即 callee

1
2
3
4
5
6
7
8
import java

from MethodCall mc
where
mc.getMethod().hasName("equals") and
mc.getArgument(0).(StringLiteral).getValue() = "" and
mc.getQualifier().getType() instanceof TypeString
select mc, "This comparison to empty string is inefficient, use isEmpty() instead."

再例如,创建一个 python 项目的 codeql-database 命令如下:

1
codeql database create ./salt-codeql-database --language=python --source-root ./salt-master

再写入如下 ql 语句,查找函数参数数量大于7的函数

1
2
3
4
5
import python

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

CodeQL 参考文档

  1. codeql for java:在 Java 项目中进行简单的 CodeQL 查询,常用的类都有一些例子,具备参考价值。
  2. QL language reference :QL 查询语言教程,列举了 Predicates、Types 等类的广义用法,比上面的更全面。
  3. standard librares for java :有关 codeql for java 的所有类和方法的文档,过于全面。
  4. CWE for Java : 漏洞扫描脚本,包括 JNDI、log4j等,可作为查询编写的参考,在 java-queries/securiy 目录下。