新版本 codeql 学习
新版本 codeql 学习
写在最前面
之前一直听闻codeql大名,现在准备花点时间好好学习一下这个静态代码分析神器
不过codeql老版本和新版本的语法区别较大,网上文章也彼此不同,所以我只能自己翻找官方文档去学
安装codeql
参考 https://xz.aliyun.com/news/16918
创建数据库
以 京麒CTF 的 fastj 为例
使用jadx对他进行反编译。 对目标的类创建数据库
1 | codeql database create fastj --language=java --source-root=./sources --build-mode=none |
成功创建数据库后在规则库下打开
在这个rule下/java/ql/src 这种标准目录下 创建语句 就可以开始愉快的使用codeql拉(我这个路径有些问题的,如果不这样会导致codeql的库倒入失败 然后就是一堆报错各种问题 前面调用一些方法的时候还是无所谓 但是后面写的多了就各种问题 所以我决定换到正确的目录)
语法学习
基本语法
codeql 的常见查询语句结构如下
1 | import <language> /* 导入对应的语言包 */ |
常用的如下
名称 | 解释 |
---|---|
Method | 方法类,Method method表示获取当前项目中所有的方法 |
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 | import java |
常见方法
方法 | 返回类型 | 说明 | 例子 |
---|---|---|---|
getLocation | string | 获取元素路径 | |
getCallee | method | 返回调用了xx方法的对象 | |
getCaller | method | 获取调用此调用的可调用对象。 | |
getNumArgument | Int | 获取到此调用中提供的参数数字 | |
getAnArgument | 获取此调用中的参数 |
RefType
储存了Class和interface的声明
常用方法
方法 | 说明 | 例子 |
---|---|---|
getASupertype*() | 查找继承关系 | |
hasQualifiedName | 查找类名 | |
getASupertype*() | 获取所有父类/接口 | |
instanceof | 判断某个 Type 是否是 RefType |
1 | import java |
如果只需要寻找class 可以使用Class
predicate
predicate 帮助我们封装某一连串的逻辑,类似于写一个判断函数,方便我们去缩短查询语句
有点类似if |前后存在上下文关系,并列关系可以用 and 或者 or
1 | import java |
类
类用来代表符合某种逻辑的值,可以用类来表示一类有着相似特性的值,基本格式如下
1 | class MyClass extends SomeType { |
例 表示类里面存在有一个toString方法
1 | import java |
常用的一些函数
当是学习后练手了 顺便封装起来 方便我之后调用
查询所有的setter方法 和包含这些setter方法的类
1 | import java |
查询所有的getter方法 和包含这些getter的类
1 | class Hasgetterclass extends Class { |
查询所有的无参构造方法
1 | class Noparmconstructor extends Constructor{ |
数据流分析
1 | 贴一些大佬的文章吧,我就不瞎写了 |
在 CodeQL 中,数据流(data flow) 是用于追踪数据从源(source)传到汇(sink)的路径,可以由此查询输入的数据最终能否流入到我们想要的目标内
数据流
codeql提供了两种数据流 本地数据流和全局数据流,本地数据流的作用域限定在一个方法内、一个调用内。本地数据库需要导入
1 | import semmle.code.java.dataflow.DataFlow |
全局数据流比本地数据流更强大,但是执行时也更消耗时间与内存。
source and sink
新版本的codeql 要求 我们使用模块完成对source和sink 的封装,该模块需要实现isSource 和 isSink 两个方法
1 | module test implements DataFlow::ConfigSig { |
对于source 可以选择自己设置某个函数获取的值 比如调用getParameter 获取到的参数
1 | predicate isSource(DataFlow::Node source) { |
我们也可以指定某一个变量 作为source 在fastj 这道题中 我们可以控制的参数为json
我们把这个变量当做我们的source
1 | exists(Parameter pa ,Method m | |
codeql 也为我们提供了一个比较完整的source 我们可以直接调用这个source
1 | predicate isSource(DataFlow::Node source) { |
对于sink 来说也是一样
1 | predicate isSink(DataFlow::Node sink) { |
官方给出了很多的直接可以使用的api 这里不过多赘述
我们需要使用创建一个全局的数据流跟踪引擎
1 | module GenericTaintFlow = TaintTracking::Global<Test>; |
这样就完成了一个数基本的查询
过滤器
有时候会出现一个不正确的数据流 我们可以进行消毒处理
1 | predicate isBarrier(DataFlow::Node node) { |
附加流
我们可以设置其他的流处理方法
如处理tostring
1 | predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) { |