入门

首先给出我学习的网站

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

然后我们可以把下载好的这个文件夹添加为环境变量

然后命令行检测一下

codeql入门 - 图1

SDK安装

https://github.com/Semmle/ql

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

codeql入门 - 图2

vscode插件安装

codeql入门 - 图3

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

codeql入门 - 图4

测试”hello world”

生成Database

为了测试我们刚才的开发环境是否可以正常调试,我们实现一个简单的”Hello World”。

由于CodeQL的处理对象并不是源码本身,而是中间生成的AST结构数据库,所以我们先需要把我们的项目源码转换成CodeQL能够识别的CodeDatabase

我们使用如下命令进行CodeDatabase的生成工作。

  1. 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
  1. 我们来解释一下上面生成database命令的语句:
  2. codeql database create ~/CodeQL/databases/codeql_demo 当然是指我们要创建的database为~/CodeQL/databases/codeql_demo(注意:要先创建~/CodeQL/databases/目录)。
  3. --language="java" 表示当前程序语言为Java
  4. --command="mvn clean install --file pom.xml" 编译命令(因为Java是编译语言,所以需要使用–command命令先对项目进行编译,再进行转换,pythonphp这样的脚本语言不需要此命令)
  5. --source-root=~/CodeQL/micro-service-seclab/ 这个当然指的是项目路径

我们执行以上命令后,首先会对项目进行编译,然后就会提示创建数据库成功,访问C:\Users\Yang_99\Desktop\CodeQL\test\micro-service-seclab-database即可

这就是一个codeql数据库。我们要对它进行分析。

codeql入门 - 图5

导入Database

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

codeql入门 - 图6

codeql入门 - 图7

编写Helloworld

我们打开刚才下载的SDK目录(用vscode打开)

这个表示输出Hello world

codeql入门 - 图8

在这个目录下新建demo.ql 然后右键运行,那么就成功搭建了codeql。

QL语法

CodeQL的查询语法有点像SQL,如果你学过基本的SQL语句,基本模式应该不会陌生。

第一行表示我们要引入CodeQl的类库,因为我们分析的项目是java的,所以在ql语句里,必不可少。

from int i,表示我们定义一个变量i,它的类型是int,表示我们获取所有的int类型的数据;

where i = 1, 表示当i等于1的时候,符合条件;

select i,表示输出

QL查询的语法结构为:

  1. from [datatype] var
  2. where condition(var = something)
  3. select var

类库

上面我们提到,我们需要把我们的靶场项目,使用CodeQL引擎转换成CodeQL可以识别的database(micro-service-seclab-database),这个过程当中,CodeQL引擎把我们的java代码转换成了可识别的AST数据库。

codeql入门 - 图9

要先选择为当前数据库,然后生成AST

AST Code大体长这个样子:

codeql入门 - 图10

我们的类库实际上就是上面AST的对应关系。

怎么理解呢?比如说我们想获得所有的类当中的方法,在AST里面Method代表的就是类当中的方法;比如说我们想过的所有的方法调用,MethodAccess获取的就是所有的方法调用。

我们经常会用到的ql类库大体如下:

名称 解释
Method 方法类,Method method表示获取当前项目中所有的方法
MethodAccess 方法调用类,MethodAccess call表示获取当前项目当中的所有方法调用
Parameter 参数类,Parameter表示获取当前项目当中所有的参数

结合ql的语法,我们尝试获取micro-service-seclab项目当中定义的所有方法:

  1. import java
  2. from Method method
  3. select method

codeql入门 - 图11

我们再通过Method类内置的一些方法,把结果过滤一下。比如我们获取名字为getStudent的方法名称。

  1. import java
  2. from Method method
  3. where method.hasName("getStudent")
  4. select method.getName(), method.getDeclaringType()

codeql入门 - 图12

method.getName() 获取的是当前方法的名称

method.getDeclaringType() 获取的是当前方法所属class的名称。

谓词

和SQL一样,where部分的查询条件如果过长,会显得很乱。CodeQL提供一种机制可以让你把很长的查询语句封装成函数。

这个函数,就叫谓词。

语法解释

  1. predicate 表示当前方法没有返回值。
  2. 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中我们通过

  1. override predicate isSource(DataFlow::Node src) {}

方法来设置source

思考一下,在我们的靶场系统(micro-service-seclab)中,source是什么?

我们使用的是Spring Boot框架,那么source就是http参数入口的代码参数,在下面的代码中,source就是username:

  1. @RequestMapping(value = "/one")
  2. public List<Student> one(@RequestParam(value = "username") String username) {
  3. return indexLogic.getStudent(username);
  4. }

在下面的代码中,source就是Student user(user为Student类型,这个不受影响)。

  1. @PostMapping(value = "/object")
  2. public List<Student> objectParam(@RequestBody Student user) {
  3. return indexLogic.getStudent(user.getUsername());
  4. }

本例中我们设置Source的代码为:

  1. override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }

设置Sink

在CodeQL中我们通过

  1. override predicate isSink(DataFlow::Node sink) {
  2. }

方法设置Sink。

在本案例中,我们的sink应该为query方法(Method)的调用(MethodAccess),所以我们设置Sink为:

  1. override predicate isSink(DataFlow::Node sink) {
  2. exists(Method method, MethodAccess call |
  3. method.hasName("query")
  4. and
  5. call.getMethod() = method and
  6. sink.asExpr() = call.getArgument(0)
  7. )
  8. }

注:以上代码使用了exists子查询语法,格式为exists(Obj obj| somthing), 上面查询的意思为:查找一个query()方法的调用点,并把它的第一个参数设置为sink。
在靶场系统(micro-service-seclab)中,sink就是:

  1. jdbcTemplate.query(sql, ROW_MAPPER);

因为我们测试的注入漏洞,当source变量流入这个方法的时候,才会发生注入漏洞!

Flow数据流

设置好Source和Sink,就相当于搞定了首尾,但是首尾是否能够连通才能决定是否存在漏洞!

一个受污染的变量,能够毫无阻拦的流转到危险函数,就表示存在漏洞!

这个连通工作就是CodeQL引擎本身来完成的。我们通过使用config.hasFlowPath(source, sink)方法来判断是否连通。

比如如下代码:

  1. from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
  2. where config.hasFlowPath(source, sink)
  3. select source.getNode(), source, sink, "source"

我们传递给config.hasFlowPath(source, sink)我们定义好的source和sink,系统就会自动帮我们判断是否存在漏洞了。

初步成果

在CodeQL中,我们使用官方提供的TaintTracking::Configuration方法定义source和sink,至于中间是否是通的,这个后面使用CodeQL提供的config.hasFlowPath(source, sink)来帮我们处理。

  1. class VulConfig extends TaintTracking::Configuration {
  2. VulConfig() { this = "SqlInjectionConfig" }
  3. override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
  4. override predicate isSink(DataFlow::Node sink) {
  5. exists(Method method, MethodAccess call |
  6. method.hasName("query")
  7. and
  8. call.getMethod() = method and
  9. sink.asExpr() = call.getArgument(0)
  10. )
  11. }
  12. }

CodeQL语法和Java类似,extends代表集成父类TaintTracking::Configuration。

这个类是官方提供用来做数据流分析的通用类,提供很多数据流分析相关的方法,比如isSource(定义source),isSink(定义sink)

src instanceof RemoteFlowSource 表示src 必须是 RemoteFlowSource类型。在RemoteFlowSource里,官方提供很非常全的source定义,我们本次用到的Springboot的Source就已经涵盖了。