初逢CodeQL

本文最后更新于:10 天前

参考文章:

codeql官方文档说明:

https://docs.github.com/zh/code-security/codeql-cli/getting-started-with-the-codeql-cli/about-the-codeql-cli

按照官方文档的要求,下载成功codeql-bundle后,将codeql路径配置到环境变量中

codeql的简单工作原理是通过记录程序在编译过程中生成的语法树,并形成关联,构建成一个数据库,数据库中包含项目代码的所有基本信息(有什么类、类继承了什么、实现了什么、什么修饰符,类里有什么方法、什么属性,方法里调用了什么方法、属性,方法的返回值是什么等等。。。)方便我们通过代码审计寻找bug、漏洞

那么一切准备就绪后,第一件事要做的自然就是生成一个数据库

codeql支持相当多的语言(悲惨的php被排除在外了),但是我的目的主要还是用来审计java代码,以及寻找一些反序列化过程中的gadget利用链,所以初步学习还是拿java开刀了

话不多说,开始

创建数据库

https://docs.github.com/zh/code-security/codeql-cli/getting-started-with-the-codeql-cli/preparing-your-code-for-codeql-analysis

构建一个数据库只需要以下一个命令:

1
codeql database create <数据库名> --source-root=<待分析的项目路径> --language=<代码语言> --command=<编译代码>

--source-root参数若不指定则默认当前目录下为项目代码根目,有时候--command也可以不指定会自动用maven帮我们编译打包

vscode导入数据库并查询

codeql高效使用需结合vscode的codeql插件,安装完成后侧边栏会出现一个QL的图标

在settings中配置好我们的可执行文件的目录

安装好插件之后,我们先把目光转移到下面这个目录:

codeql根目录/qlpacks/codeql,在这个目录下有非常多的内置ql查询语句:

我们首先就需要使用vscode打开一个书写ql语句的文件,可以是自己编写的,也可以是内置的,比方说我们就使用vscode打开这个内置ql语句目录

使用vscode打开codeql根目录/qlpacks/codeql,然后在database处选择通过上一步生成的数据库:

——————时隔半年后继续来更新——————

主要参考文章:https://codeql.github.com/docs/codeql-language-guides/basic-query-for-java-code/

练习推荐使用项目:https://github.com/l4yn3/micro_service_seclab/

实战项目

有时候获取到的可能并非源码,而是jar包,但是jar包反编译后的源码再次重编译会因为种种原因编译错误,在一次和一位高手沟通的过程中我了解到也可以不通过编译来构建codeql数据库

模拟大部分情况下获取jar包的情况,先打包后反编译

1
java -jar java-decompiler.jar D:\Downloads\micro_service_seclab\target\micro-service-seclab-0.0.1-SNAPSHOT.jar D:\Downloads\micro_service_seclab\decompile

反编译后得到一个jar包,不过解压后会发现里面的文件不再是class而是java

创建数据库

1
codeql database create D:\Downloads\micro_service_seclab\database --language java -s D:\Downloads\micro_service_seclab\decompile\micro-service-seclab-0.0.1-SNAPSHOT --overwrite --build-mode none

打开vscode,按上面的方式导入数据库后执行个select 111看看效果

codeql的语法几乎和sql差不多,不过是from ** where ** select **这样的

create query

简单写一个语句用于查询名为getTeacherById的方法以及返回当前方法所在class

codeql的封装

codeql本身支持一种类似于面向对象的封装方式

谓词

如果where条件的部分过长会导致可读性降低,在codeql中可以将其封装为一个函数,这个函数就称为谓词

下面的语句和上面的语句功能是相同的,都是ql里谓词的一些常见用法

predicate是一个可以被调用用于表述某种事实或者关系条件的规则,参数可选,可返回true或者false,

1
2
3
4
5
6
7
8
9
10
11
import java


predicate getTeacherById(Method method){
method.getName() = "getTeacherById"
}


from Method method
where getTeacherById(method)
select method.getName(), method.getDeclaringType()

或者

1
2
3
4
5
6
7
8
9
10
11
import java


predicate getTeacherById(Method method){
exists(|method.hasName("getTeacherById") )
}


from Method method
where getTeacherById(method)
select method.getName(), method.getDeclaringType()

class

codeql里的class用于定义一个类型的子类(如继承了Method、Function等),用于定义一类的方法或者函数

例如在fastjson反序列化利用中,我们可能需要寻找一类setter或者getter

对于getter,需要满足几个条件:

  • 没有参数
  • 方法名以get开头且不为get
  • 修饰符是public

于是可以定义

1
2
3
4
5
6
7
8

class FastjsonGetter() extends Method{
FastjsonGetter(){
this.getName().indexOf("get") &
this.getName().length() > 3 &
this.isPublic() &
}
}

source

source即利用链的输入端,也就是入口点,codeql在最新版api的变动比较大,网上大部分教程也相对比较旧比较少,因此很多语句都不适用新的api,让ai帮忙写的语句也几乎是跟不上版本了

关于新的api中查询的语句,可以参考以下一些官方文章

常用的一些关键词

对于方法调用MethodCall

  • getMethod()获取对应方法定义
  • getQualifier()获取调用者(进一步可以getType()获取调用者的类型,使用instanceof来判断)
  • getCaller()获取调用该方法的方法
  • getCallee()获取被调用方法,即这个方法本身
  • getArgument(0)方法获取第一个参数(修改index获取其他的),强转为(StringLiteral)如mc.getArgument(0).(StringLiteral).getValue()可筛选出类型为String的并获取特定的字符串值(如”%s”)。在predicate谓词里使用mc.getArgument(0).getType().getName() = "URL"可筛选特定类型参数的方法调用

对于方法定义Method

  • hasName(“equals”)获取方法名为equals的方法