新版本 codeql 学习

写在最前面

之前一直听闻codeql大名,现在准备花点时间好好学习一下这个静态代码分析神器

不过codeql老版本和新版本的语法区别较大,网上文章也彼此不同,所以我只能自己翻找官方文档去学

安装codeql

参考 https://xz.aliyun.com/news/16918

创建数据库

以 京麒CTF 的 fastj 为例

image-20250525145229513

使用jadx对他进行反编译。 对目标的类创建数据库

1
codeql database create fastj --language=java --source-root=./sources --build-mode=none 

成功创建数据库后在规则库下打开

image-20250525155942896

在这个rule下/java/ql/src 这种标准目录下 创建语句 就可以开始愉快的使用codeql拉(我这个路径有些问题的,如果不这样会导致codeql的库倒入失败 然后就是一堆报错各种问题 前面调用一些方法的时候还是无所谓 但是后面写的多了就各种问题 所以我决定换到正确的目录)

image-20250525160235684

语法学习

基本语法

codeql 的常见查询语句结构如下

1
2
3
4
5
6
import <language> /* 导入对应的语言包 */ 
/* 一些谓词、类的设置 即定义一些方法或者类*/

from /* 声明变量等 即定义各种变量*/
where /* 设置逻辑表达式 即代码逻辑*/
select /* 打印结果 即输出*/

常用的如下

名称 解释
Method 方法类,Method method表示获取当前项目中所有的方法
MethodAccess 方法调用类,MethodAccess call表示获取当前项目当中的所有方法调用(此方法在最新版本的codeql被弃用)
MethodCall 方法调用是具有参数列表的方法调用。
Parameter 参数类,Parameter表示获取当前项目当中所有的参数名称
Expr 表达式
RefType 储存了Class和interface的声明

全部的逻辑可以在这里看到

1
https://codeql.github.com/codeql-standard-libraries/java/index.html#M

常用的函数方法

Method

这里仅作列出一些常见的 不举出例子

方法 返回类型 说明 例子
getName() string 方法名,如 doLogin
getQualifiedName() string 完整限定名,如 com.example.UserService.doLogin
getDeclaringType() Type / Class 所属的类或接口
hasModifier(string) boolean 判断是否有某个修饰符,如 “static”、”public” method.hasModifier(“static”)
isStatic() boolean 是否为静态方法 method.isStatic()
isAbstract() boolean 是否为抽象方法
getAParameter() Parameter 获取方法的参数
getReturnType() Type 返回值类型
getBody() Block 获取方法体(可用于检查内部语句)
getNumberOfLinesOfCode int 获取此元素范围涵盖的代码行数
getACallee methods 获取可从此可调用对象调用的被调用方法
hasName(string) 查询存在。名字的

需要注意的是 如果需要寻找构造方法 使用Constructor

Call

通过call可以查找是谁调用了这个方法

1
2
3
4
5
import java

from Constructor method,Call call
where method.getName()="FilterFileOutputStream" and call.getCallee()=method
select method,call

常见方法

方法 返回类型 说明 例子
getLocation string 获取元素路径
getCallee method 返回调用了xx方法的对象
getCaller method 获取调用此调用的可调用对象。
getNumArgument Int 获取到此调用中提供的参数数字
getAnArgument 获取此调用中的参数

RefType

储存了Class和interface的声明

常用方法

方法 说明 例子
getASupertype*() 查找继承关系
hasQualifiedName 查找类名
getASupertype*() 获取所有父类/接口
instanceof 判断某个 Type 是否是 RefType
1
2
3
4
5
6
7
8
9
10
11
12
import java

from RefType r
where r.hasName("FileOutputStream")
select r.getACallable(),r.getAMember(),r.getAField(),r.getAMethod(),r.getASupertype(),r.getAnAncestor()
/*
getACallable() 获取所有可以调用方法(其中包括构造方法)
getAMember() 获取所有成员,其中包括调用方法,字段和内部类这些
getAField() 获取所有字段
getAMethod() 获取所有方法
getASupertype() 获取父类
getAnAncestor() 获取所有的父类相当于递归的getASupertype*() */

如果只需要寻找class 可以使用Class

predicate

predicate 帮助我们封装某一连串的逻辑,类似于写一个判断函数,方便我们去缩短查询语句

有点类似if |前后存在上下文关系,并列关系可以用 and 或者 or

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


predicate findname(Method method) {
exists( | method.hasName("getflag"))

}

from Method method
where findname(method)
select method


image-20250526160038929

类用来代表符合某种逻辑的值,可以用类来表示一类有着相似特性的值,基本格式如下

1
2
3
4
5
6
7
8
9
10
11
class MyClass extends SomeType {
MyClass() {
// 构造约束条件
this.someProperty() = someValue
}

predicate myPredicate() {
// 类内谓词
}
}

例 表示类里面存在有一个toString方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java


class Tostringclass extends Class{
Tostringclass(){
exists(Method m |
m.getName() = "toString" and
m.getDeclaringType() = this and
m.getNumberOfParameters() = 0
)
}
}

from Tostringclass c
select c

常用的一些函数

当是学习后练手了 顺便封装起来 方便我之后调用

查询所有的setter方法 和包含这些setter方法的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java

class Hassetterclass extends Class {
Hassetterclass(){
exists(Method m |
m.getDeclaringType() = this and
m.getName().matches("set%") )
}

}


from Hassetterclass c, Method m
where
m.getDeclaringType() = c and
m.getName().matches("set%")
select c, m.getName()

image-20250610155519515

查询所有的getter方法 和包含这些getter的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Hasgetterclass extends Class {
Hasgetterclass(){
exists(Method m |
m.getDeclaringType() = this and
m.getName().matches("get%") )
}

}

from Hasgetterclass c, Method m
where
m.getDeclaringType() = c and
m.getName().matches("get%")
select c, m.getName()

查询所有的无参构造方法

1
2
3
4
5
6
7
8
class Noparmconstructor extends Constructor{
Noparmconstructor(){
this.getNumberOfParameters() = 0
}
}

from Noparmconstructor c
select c.getName(),c.getDeclaringType()

数据流分析

1
2
3
贴一些大佬的文章吧,我就不瞎写了
https://geekdaxue.co/read/loulan-b47wt@rc30f7/hphtnp
这个大佬讲的真的很好 但是有些api在最新版本的codeql中已经使用不了了 api还是得自己翻找

在 CodeQL 中,数据流(data flow) 是用于追踪数据从源(source)传到汇(sink)的路径,可以由此查询输入的数据最终能否流入到我们想要的目标内

数据流

codeql提供了两种数据流 本地数据流和全局数据流,本地数据流的作用域限定在一个方法内、一个调用内。本地数据库需要导入

1
import semmle.code.java.dataflow.DataFlow

全局数据流比本地数据流更强大,但是执行时也更消耗时间与内存。

source and sink

新版本的codeql 要求 我们使用模块完成对source和sink 的封装,该模块需要实现isSource 和 isSink 两个方法

1
2
3
4
5
6
7
8
module test implements DataFlow::ConfigSig {  
predicate isSource(DataFlow::Node source) {
source
}
predicate isSink(DataFlow::Node sink) {
sink
}
}

对于source 可以选择自己设置某个函数获取的值 比如调用getParameter 获取到的参数

1
2
3
4
5
6
7
predicate isSource(DataFlow::Node source) {
exists( MethodCall m |
m.getMethod().getName() = "getParameter" and
m.getMethod().getDeclaringType().getName() = "javax.servlet.http.HttpServletRequest" and
source.asExpr() = m
)
}// 这里获取到了getParamete 获得的所有参数

我们也可以指定某一个变量 作为source 在fastj 这道题中 我们可以控制的参数为json

image-20250611182643242

我们把这个变量当做我们的source

1
2
3
4
exists(Parameter pa ,Method m | 
pa.hasName("json") and m .getName() = "fastj" and pa.getCallable()=m and
source.asExpr() = pa.getAnAccess()
)

codeql 也为我们提供了一个比较完整的source 我们可以直接调用这个source

1
2
3
predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource
}

image-20250611184727629

对于sink 来说也是一样

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

官方给出了很多的直接可以使用的api 这里不过多赘述

我们需要使用创建一个全局的数据流跟踪引擎

1
2
3
4
5
6
7
8
module GenericTaintFlow = TaintTracking::Global<Test>;

import GenericTaintFlow::PathGraph

from GenericTaintFlow::PathNode source, GenericTaintFlow::PathNode sink
where GenericTaintFlow::flowPath(source, sink)
select source.getNode(), source, sink

这样就完成了一个数基本的查询

过滤器

有时候会出现一个不正确的数据流 我们可以进行消毒处理

1
2
3
4
predicate isBarrier(DataFlow::Node node) {
node.getType() instanceof PrimitiveType
// exists(Method sanitizer | sanitizer.hasName("encode|filter") and ...)
}

附加流

我们可以设置其他的流处理方法

如处理tostring

1
2
3
4
5
6
7
8
predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
exists(MethodCall ma |
ma.getMethod().getName() = "toString" and
ma.getQualifier() = n1.asExpr() and
ma = n2.asExpr()
)
}
}