壹 CodeQL简介
1.1 介绍
在做代码审计的时候,会用到通过关键字查询的方式进行代码审计,但是这样做的弊端就是搜索出来的结果量太大了,而且可能项目里用不到开发者重写的危险函数(例如文件上传,可能项目之前有这个功能,后来换了一些架构就不用了,但是忘记删除了),这时候就在想是否是有一种方法或者规则集,进行过滤,也就是说通过自动化或者半自动化的方式对代码的漏洞点进行筛查,答案是有的。
在说这种新型的代码审计方法之前,我们需要了解一个知识点:在代码自动化安全审计的理论当中,有一个最核心的三元组概念,就是source
、sink
和sanitizer
。
source
:漏洞污染链条的输入点,比如获取http
请求的参数部分,就是非常明显的source
。sink
:漏洞污染链条的执行点,比如SQL
注入漏洞,最终执行SQL
语句的函数就是sink
,这个函数可能是query
或者exeSql
,或者其它。sanitizer
:又叫净化函数,是指在整个的漏洞链条当中,如果存在一个方法阻断了整个传递链,使得source
的恶意数据传递到sink
点后,这些恶意数据已经被过滤过了或者说是被净化过了,那么这个方法就叫sanitizer
。
下面是大概的流程图:
# flow一般可以理解为数据从source到sink之间的流向经过的方法,有一些文章叫node(其实我们可以理解为node是一个一个的函数,然后多个node组成了flow这个数据流),如果flow存在一个方法阻断了整个传递链,那么这个方法就是sanitizer
source(污点的来源、可控的参数点)-> flow(一些对传入的参数进行编码或者过滤的过程)-> sink(漏洞触发点、触发漏洞的方法)
根据前面的知识点,我们可以通过一套规则集进行查询匹配,例如我们将出现危险方法的地方为终点(sink
),用户输入的数据为起点的规则(source
),进行查询,这时候就提取出满足我们需要审计的一个可疑利用链,再通过人工进行审核,即可高效的完成代码审计(当然在实际应用中并不会这么简单),这个过程类似与数据库的查询,通过SQL
语句去查询我们需要的内容,这个想法就是CodeQL
的核心理念所在。而CodeQL
就是一个会提取源码中的相关信息以及数据之间的相互关系,并根据这些信息生成数据库的强大的静态代码分析工具,在使用CodeQL
时,我们就是将代码分析过程转化成了数据库查询过程,可以执行CodeQL
查询(简称QL
)来查询代码库。很多人用它都会利用已知的安全漏洞来挖掘类似的漏洞,但是个人觉得我们可以通过它来进行一些关键信息搜索,然后挖掘出自己独创的漏洞,因为这个工具本质就是一个代码搜索工具,与人工不同在于它可以进行source
和sink
数据流闭环。
Github
为了解决其托管的海量项目的安全性问题,收购了CodeQL
的创业公司,并宣布开源CodeQL
的规则部分,这样全世界的安全工程师就可以贡献高效的QL
审计规则给Github
,帮助它解决托管项目的安全问题,所以除了官方的规则库外,我们也可以自定义规则完善我们的规则库。
1.2 原理
CodeQL
的整体思路是把源代码转化成一个可查询的数据库,通过Extractor
模块对源代码工程进行关键信息分析提取,提取完成后,所有分析所需的数据都会导入一个文件夹,这个就是CodeQL
数据库,其中包括了源代码文件、关系数据、语言相关的database schema
(schema
定义了数据之间的相互关系)。CodeQL
的数据库并没有使用现有的数据库技术,而是一套基于文件的自己的实现。
不同类型代码生成的逻辑也不同:
- 对于编译型语言,
Extractor
会监控编译过程,编译器每处理一个源代码文件,它都会收集源代码的相关信息,如:语法信息(AST
抽象语法树)、语意信息(名称绑定、类型信息、运算操作等)、控制流、数据流等,同时也会复制一份源代码文件。- 对于解释性语言,
Extractor
则直接分析源代码,得到类似的相关信息。
CodeQL
的工作流程如下图所示:
整体的工作流程分为四个模块:
提取数据库(Extraction
):对源代码工程进行关键信息分析提取,构成一个关系型数据库CodeQL database
。
编译QL规则(Query Compilation
):将QL
规则结合QL
标准库编译,生成查询的执行计划。
执行QL查询(Query Evaluation
):在生成的CodeQL
数据库上运行QL
查询。
展示查询结果(Query results
):最终将查询结果展示给用户,方便用户进行进一步的人工审计分析。
注意:由于CodeQL
是需要项目能正常构建,然后拦截这些在构建中出现的编译命令,然后生成基于AST
抽象语法树的数据库,所以当项目代码不能被正常构建的话是不能使用CodeQL
的(这也是CodeQL
的一个缺点之一),这里说的项目构建的意思就是Java
代码只要可以打包构建起来,然后在构建过程中可以拦截所有的编译命令就能,生成AST
抽象语法树的数据库,不一定需要正常运行。我们可以理解为代码是一堆电脑零件,构建就是组装成一个没有插电的电脑(不一定需要电脑运行起来),这时候CodeQL
就可以将这些零件进行链接形成一个数据库,但是如果是散开的就无法进行链接索引。由于CodeQL
主要做的是静态分析,只分析源代码,所以不依赖于项目是否运行起来,但是对于编译型语言来说,必须先能正确构建项目。
贰 安装
CodeQL
包含两部分:解析引擎、SDK
规则库。
首先我们选一个合适的路径然后创建CodeQL
目录,然后在该目录下创建codeql_Tool
、codeql_Rule
、databases
和source
目录,分别用于安装Codeql
解析引擎、SDK
规则库、生成的源码数据库和被分析源代码,当然databases
和source
目录可以在别的位置,这里我只是喜欢放在一起做归档,看个人喜好:
接着下载对应系统的CodeQL
解析引擎,然后在codeql_Tool
下解压,将CodeQL
可执行程序添加到环境变量中,方便命令行运行:
设置好后,输入Codeql
出现下面内容,说明引擎安装完成:
然后下载SDK
规则库,将其放到codeql_Rule
中:
最后安装Visual Studio Code
代码编辑器的CodeQL
插件(CodeQL
需要使用Visual Studio Code
来开发和调试规则),配置一下上面我们安装的CodeQL
引擎路径,完成CodeQL
安装:
叁 基础使用
3.1 准备分析的代码
首先准备要扫描的代码,这里我们用micro_service_seclab
这个靶场来做测试和教学(注意:需要切换到master
分支,默认为main
分支,存在异常)。将源码解压放到source
目录中:
3.2 生成数据库
由于CodeQL
只可以对被解析引擎编译生成的抽象语法树(AST
)结构数据库进行扫描,所以需要先生成目标数据库。而生成数据库分为两种:本地生成和在线生成。
- 本地生成数据库:一般适合编译类语言,如
go
、Java
、Golang
等,这种方式需要按照前面的安装步骤去安装CodeQL
引擎和规则库。 - 在线生成数据库:一般适合解释性语言,如
Python
等,而这种方式完全不需要安装CodeQL
引擎,只需要将代码放到dashboard网站上生成数据库,然后下载官方规则库。
注意:运行CodeQL
生成数据库,前提是需要被审计的系统能正常打包或者正常构建起来。
3.2.1 本地生成(推荐)
codeql database create 数据库名 --language=cpp --source-root=源码路径 --command="编译命令"
数据库名:代码数据库存放目录,可以相对路径也可以绝对路径,如果没有会自动创建目录
-command:参数如果不指定,会使用默认的编译命令和参数,一般编译型语言需要这个目录,python这种解释性语言可以不需要,像Java可能就需要设置mvn clean install --file pom.xml这种命令编译
-source-root:源码路径,可以相对路径也可以绝对路径
-overwrite:表示 create 的目标 database 对已有的 database 做覆盖
-language:要根据具体项目的编译语言指定
# 例子,打开终端直接运行下面命令
codeql database create 代码数据库存放目录 --language=java --command='mvn clean install --file pom.xml' --source-root=源代码目录 --overwrite
出现Successfully
说明生成完成(注意项目代码要能打包或者运行起来,其实我们运行完后就会发现源码下多出了打包的程序):
3.2.2 在线生成(copy网上的教程,没用过)
没有找到对应功能,这里就用网上文章吧(后面有时间再研究): 代码分析平台CodeQL学习手记(一)
3.2.3 语言对应关系
其中语言对应关系如下:
Language |
Identity |
---|---|
C/C++ |
cpp |
C# |
csharp |
Go |
go |
Java |
java |
javascript/Typescript |
javascript |
Python |
python |
CodeQL
支持的语言是有限的,详细可以参考:CodeQL
支持的语言和框架
3.2 选择规则
再进行项目漏洞扫描前,我们需要选择规则来扫描,这里有两种:
- 选择
CodeQL
内部的对应语言规则 - 创建自定义规则
3.2.1 选择CodeQL内部的对应语言规则
如果选择CodeQL
内部规则,那么我们需要在对应规则文件夹下打开Vscode
,这里我们把全部规则打开,方便寻找,我的CodeQl
规则在.\Codeql\codeql_Rule\
下,其中.ql
后缀的文件都是规则文件,就是我们用于扫描代码的:
到这里我们就选择了官方的CodeQL
规则。
3.2.2 创建自定义规则
如果我们需要创建自定义规则,首先创建一个新的自定义规则文件夹或者叫QL
包,这里我们可以设置个名字Test_rule
,然后再QL
包下创建一个qlpack.yml
文件(必需),内容如下:
name: test-query
version: 0.0.1
libraryPathDependencies: codeql-java
需要注意:
QL
包目录的根目录下必须包含一个以qlpack.yml
命名的文件,该文件包含name
、version
以及libraryPathDependencies
字段属性。只有当自定义的QL
包有qlpack.yml
文件才能对对应语言进行正常编译、执行。
参考:https://codeql.github.com/docs/codeql-cli/using-custom-queries-with-the-codeql-cli/
到这里我们就创建了自定义的CodeQL
规则文件夹。
3.3 进行项目漏洞扫描
选择好规则后,我们就要通过QL
语句去扫描项目,有两种方式:
- 通过
Vscode
对项目进行扫描 - 通过命令行对项目进行扫描
3.3.1 通过Vscode对项目进行扫描
这里我们用自定义的QL
包文件夹来扫描,在刚刚的QL
包文件夹内打开Vscode
,然后选择CodeQL
(如果用官方规则也是一样的步骤):
添加刚刚生成的数据库到Database
中:
会出现库信息:
接着右键点击Add Database Source to Workspace
将其加入到工作区中(为了方便查看以及代码定位):
接着新建一个test.ql
文件,内容如下:
select "hello word"
右键点击Run Query
运行(这里有好几个Run Query
,根据个人需求选择即可),这只是一个简单输出hello word
:
我们添加搜索Java
模块,修改内容为:
// 内容可以不需要了解,只需要知道这是QL语言
import java
from Call c
select c,"This is a method call!"
右键点击Run Query
运行,由于我们加入了Java
模块,这里就会对我们加载的源代码数据库进行搜索:
当然也可以选择多个规则文件进行扫描,这里我们在规则所在文件夹里,右键点击CodeQL:Run Queries in Selected Files
,弹窗提示是否使用这些规则,点击Yes
即可。
3.3.2 通过命令行对项目进行扫描
当我们需要用命令行对项目进行扫描时,我们可以通过codeql analyze
命令去执行单个QL
文件、目录下所有QL
文件或者查询suite(.qls)
,其实我们用vscode
编写ql
规则和验证的时候,也是用到了codeql
命令行操作,只不过vscode
代替我们执行了命令。
# 这里以java为例子
# 执行单个规则
codeql database analyze 生成的数据库 codeql/ql/java/ql/examples/test.ql --format=csv --output=result.csv --rerun
# 执行全部规则
codeql database analyze 生成的数据库 codeql/ql/java/ql/src/codeql-suites/java-security-extended.qls --format=csv --output=result.csv --rerun
# --format=csv:输出结果的格式,如sarif-latest可以输出json格式,csv输出csv格式,必填
# --output=result.csv:输出结果路径,必填
# 生成的数据库:分析的数据库路径,必填
# codeql/ql/java/ql/examples/test.ql:使用的查询语句,必填
出现Interpreting results.
说明扫描完成,扫描的结果在当前目录下。
3.4 规则富化
接下来我们就可以进行对输出的结果进行筛选,规则富化(就是丰富规则内容),来进一步筛查有价值的利用链或者漏洞。其实就是在前面写的规则基础上不断丰富内容,将噪点(干扰的东西)减少,最后会得到我们需要的利用链或者漏洞,这是一个不断迭代的过程。
注意:
对于自定义规则我们可以随便更改,但对于官方的规则,实际上已经很成熟了,但是难免需要修改,这时候建议是不要随意修改,而是新建一个副本,以防破坏原有的规则库,导致后面再用的时候恢复不了官方的,重新下载会比较麻烦。