入门
首先给出我学习的网站
https://www.freebuf.com/articles/web/283795.html
https://github.com/l4yn3/micro_service_seclab/
安装
引擎安装
我的安装位置是C:\Users\Yang_99\Desktop\CodeQL\
https://github.com/github/codeql-cli-binaries/releases
然后我们可以把下载好的这个文件夹添加为环境变量
然后命令行检测一下

SDK安装
我们下载SDK,同样放入CodeQL文件夹

vscode插件安装

直接安装codeql插件。然后设置好

测试”hello world”
生成Database
为了测试我们刚才的开发环境是否可以正常调试,我们实现一个简单的”Hello World”。
由于CodeQL的处理对象并不是源码本身,而是中间生成的AST结构数据库,所以我们先需要把我们的项目源码转换成CodeQL能够识别的CodeDatabase。
我们使用如下命令进行CodeDatabase的生成工作。
codeql database create micro-service-seclab-database1 --language="java" --command="mvn clean install -Dmaven.test.skip=true" --source-root=C:\Users\Yang_99\Desktop\CodeQL\micro_service_seclab-main
我们来解释一下上面生成database命令的语句:codeql database create ~/CodeQL/databases/codeql_demo 当然是指我们要创建的database为~/CodeQL/databases/codeql_demo(注意:要先创建~/CodeQL/databases/目录)。--language="java" 表示当前程序语言为Java。--command="mvn clean install --file pom.xml" 编译命令(因为Java是编译语言,所以需要使用–command命令先对项目进行编译,再进行转换,python和php这样的脚本语言不需要此命令)--source-root=~/CodeQL/micro-service-seclab/ 这个当然指的是项目路径
我们执行以上命令后,首先会对项目进行编译,然后就会提示创建数据库成功,访问C:\Users\Yang_99\Desktop\CodeQL\test\micro-service-seclab-database即可
这就是一个codeql数据库。我们要对它进行分析。

导入Database
和SQL语言一样,我们执行QL查询,肯定是要先指定一个数据库才可以。


编写Helloworld
我们打开刚才下载的SDK目录(用vscode打开)
这个表示输出Hello world

在这个目录下新建demo.ql 然后右键运行,那么就成功搭建了codeql。
QL语法
CodeQL的查询语法有点像SQL,如果你学过基本的SQL语句,基本模式应该不会陌生。
第一行表示我们要引入CodeQl的类库,因为我们分析的项目是java的,所以在ql语句里,必不可少。
from int i,表示我们定义一个变量i,它的类型是int,表示我们获取所有的int类型的数据;
where i = 1, 表示当i等于1的时候,符合条件;
select i,表示输出
QL查询的语法结构为:
from [datatype] varwhere condition(var = something)select var
类库
上面我们提到,我们需要把我们的靶场项目,使用CodeQL引擎转换成CodeQL可以识别的database(micro-service-seclab-database),这个过程当中,CodeQL引擎把我们的java代码转换成了可识别的AST数据库。

要先选择为当前数据库,然后生成AST
AST Code大体长这个样子:

我们的类库实际上就是上面AST的对应关系。
怎么理解呢?比如说我们想获得所有的类当中的方法,在AST里面Method代表的就是类当中的方法;比如说我们想过的所有的方法调用,MethodAccess获取的就是所有的方法调用。
我们经常会用到的ql类库大体如下:
| 名称 | 解释 |
|---|---|
| Method | 方法类,Method method表示获取当前项目中所有的方法 |
| MethodAccess | 方法调用类,MethodAccess call表示获取当前项目当中的所有方法调用 |
| Parameter | 参数类,Parameter表示获取当前项目当中所有的参数 |
结合ql的语法,我们尝试获取micro-service-seclab项目当中定义的所有方法:
import javafrom Method methodselect method

我们再通过Method类内置的一些方法,把结果过滤一下。比如我们获取名字为getStudent的方法名称。
import javafrom Method methodwhere method.hasName("getStudent")select method.getName(), method.getDeclaringType()

method.getName() 获取的是当前方法的名称
method.getDeclaringType() 获取的是当前方法所属class的名称。
谓词
和SQL一样,where部分的查询条件如果过长,会显得很乱。CodeQL提供一种机制可以让你把很长的查询语句封装成函数。
这个函数,就叫谓词。
语法解释
predicate 表示当前方法没有返回值。exists子查询,是CodeQL谓词语法里非常常见的语法结构,它根据内部的子查询返回true or false,来决定筛选出哪些数据。
设置Source和Sink
什么是source和sink
在代码自动化安全审计的理论当中,有一个最核心的三元组概念,就是(source,sink和sanitizer)。
source是指漏洞污染链条的输入点。比如获取http请求的参数部分,就是非常明显的Source。
sink是指漏洞污染链条的执行点,比如SQL注入漏洞,最终执行SQL语句的函数就是sink(这个函数可能叫query或者exeSql,或者其它)。
sanitizer又叫净化函数,是指在整个的漏洞链条当中,如果存在一个方法阻断了整个传递链,那么这个方法就叫sanitizer。
只有当source和sink同时存在,并且从source到sink的链路是通的,才表示当前漏洞是存在的。
设置Source
在CodeQL中我们通过
override predicate isSource(DataFlow::Node src) {}
方法来设置source。
思考一下,在我们的靶场系统(micro-service-seclab)中,source是什么?
我们使用的是Spring Boot框架,那么source就是http参数入口的代码参数,在下面的代码中,source就是username:
@RequestMapping(value = "/one")public List<Student> one(@RequestParam(value = "username") String username) {return indexLogic.getStudent(username);}
在下面的代码中,source就是Student user(user为Student类型,这个不受影响)。
@PostMapping(value = "/object")public List<Student> objectParam(@RequestBody Student user) {return indexLogic.getStudent(user.getUsername());}
本例中我们设置Source的代码为:
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
设置Sink
在CodeQL中我们通过
override predicate isSink(DataFlow::Node sink) {}
方法设置Sink。
在本案例中,我们的sink应该为query方法(Method)的调用(MethodAccess),所以我们设置Sink为:
override predicate isSink(DataFlow::Node sink) {exists(Method method, MethodAccess call |method.hasName("query")andcall.getMethod() = method andsink.asExpr() = call.getArgument(0))}
注:以上代码使用了exists子查询语法,格式为exists(Obj obj| somthing), 上面查询的意思为:查找一个query()方法的调用点,并把它的第一个参数设置为sink。
在靶场系统(micro-service-seclab)中,sink就是:
jdbcTemplate.query(sql, ROW_MAPPER);
因为我们测试的注入漏洞,当source变量流入这个方法的时候,才会发生注入漏洞!
Flow数据流
设置好Source和Sink,就相当于搞定了首尾,但是首尾是否能够连通才能决定是否存在漏洞!
一个受污染的变量,能够毫无阻拦的流转到危险函数,就表示存在漏洞!
这个连通工作就是CodeQL引擎本身来完成的。我们通过使用config.hasFlowPath(source, sink)方法来判断是否连通。
比如如下代码:
from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sinkwhere config.hasFlowPath(source, sink)select source.getNode(), source, sink, "source"
我们传递给config.hasFlowPath(source, sink)我们定义好的source和sink,系统就会自动帮我们判断是否存在漏洞了。
初步成果
在CodeQL中,我们使用官方提供的TaintTracking::Configuration方法定义source和sink,至于中间是否是通的,这个后面使用CodeQL提供的config.hasFlowPath(source, sink)来帮我们处理。
class VulConfig extends TaintTracking::Configuration {VulConfig() { this = "SqlInjectionConfig" }override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }override predicate isSink(DataFlow::Node sink) {exists(Method method, MethodAccess call |method.hasName("query")andcall.getMethod() = method andsink.asExpr() = call.getArgument(0))}}
CodeQL语法和Java类似,extends代表集成父类TaintTracking::Configuration。
这个类是官方提供用来做数据流分析的通用类,提供很多数据流分析相关的方法,比如isSource(定义source),isSink(定义sink)
src instanceof RemoteFlowSource 表示src 必须是 RemoteFlowSource类型。在RemoteFlowSource里,官方提供很非常全的source定义,我们本次用到的Springboot的Source就已经涵盖了。
