CodeQL 学习笔记【5】CodeQL 中 Java 库函数

650 阅读10分钟

前面了解了很多 CodeQL 的基础概念和简单的用法,接下来就要介绍与各种语言查询交互的功能如何使用了。

我这里就用 java 为例进行学习。

类 Class

我们从最简单的开始学习,回想起当初学 java 的时候,最先接触的是什么呢?没错就是类,那么在 QL 中,关于类,有哪些谓词或者说方法可以调用呢? 先看一下 QL 中关于 Class 的定义:

class Class extends ClassOrInterface {
  Class() { not isInterface(this) } 
  ...

看起来很容易理解,继承了 ClassOrInterface 类,然后排除掉 Interface 剩下的就是 Class 了呗。

我翻了一下源码,发现类的特有谓词很少,我们举个了例子:

import java

from Class c, Annotation a
where c.getName()="SqlInjectionLesson3" and c.getAnAnnotation() = a
select c, a
  • getAnAnnotation() 获取类的所有注解。

image.png

  • isAnonymous() 判断是否为匿名类。

除此之外 基本上没有啥特有的谓词了。但是我们看 Class 的继承关系图,可以看到继承关系还是比较复杂的,所以 Class 继承的方法还是比较多的,但是我们目前还不需要了解那么多。等后面拓展到这些类的时候再说。

graph BT
Class --继承--> ClassOrInterface --继承--> RefType & classorinterface["@classorinterface"] 
RefType --继承--> Type & Annotatable & Modifiable & reftype["@reftype"] 
Type --继承--> type["@type"] & Element
Element --继承--> element["@element"] & Top
Top --继承--> top["@top"]
Annotatable & Modifiable--继承--> Element

代码中用到的 getName() 是从 Element 类里继承的谓词。

后面还会介绍到类型,比如引用类型 RefType 有一个谓词 predicate hasQualifiedName(string package, string type) { 用法如下:

from Class c1
where c1.hasQualifiedName("org.owasp.webgoat.lessons.authbypass", "VerifyAccount")
select c1

简单来说,就是判断一个类、接口的全限定名称是不是 XXX。

image.png

其实类本身并没有太多的内容,接下来看看类里面的内容。

属性 Field

class Field extends Member, ExprParent, @field, Variable {
...

属性 Field 就是类或者接口的属性,或者叫成员变量也行吧。

常用的谓词有:

  • getType():获取属性的类型(至于什么是类型,下一小节将进行学习。)
  • getDeclaringType(): 获取这个属性的声明者的类(也就是类)。
  • f.getDeclaration(): 获取声明这个属性的语句。

简单测试一下用法

import java

from Class c, Field f
where c.getName() = "CSRFGetFlag" and c.getAField() = f
select f, f.getType(), f.getDeclaration()

image.png

graph BT
Field --继承--> field["@field"] & ExprParent & Variable & Member 
ExprParent --继承--> exprparent["@exprparent"]
ExprParent --继承----> Top
Member --继承--> Annotatable & Modifiable & member["@member"]
Variable --继承--> variable["@variable"] &  Annotatable & Modifiable
Annotatable & Modifiable--继承--> Element
Element --继承--> Top & element["@element"]
Top --继承--> top["@top"]

同 Class 一样,Field 本身特有的谓词也比较少,但是继承关系是相当复杂的,继承的谓词也是比较多的,等我们以后学习到它的父类的时候再继续吧。

类型 Type

众所周知,java 是一门静态类型的语言,java 中的变量都是有类型的,比如 Int、String、Byte 等等。

那么 QL 中必然要有对应的东西来表示,比如前面学习 Class 的时候,在 Class 的继承族谱里就出现了一个 RefType,这是什么呢?接下来看看 QL 如何表示 "类型",并且看看如何查询类型。

java 中有 八种基本数据类型 + 四种引用类型,那么看看 QL 中定义的类型有哪些:

// 引用类型,也就是对应 java 中的:类、接口类型、数组类型、枚举类型、注解类型,字符串型等
class RefType extends Type, Annotatable, Modifiable, @reftype {

// 错误类型,
class ErrorType extends RefType, @errortype {

// 内部类类型
class NestedType extends RefType {

// 成员类型:也就是包括属性、方法这些东西
class MemberType extends NestedType, Member {

// 基本数据类型: 也就是 `boolean`, `byte`, `short`, `char`, `int`, `long`, `float`, `double`
class PrimitiveType extends Type, @primitive {

// 空类型
class NullType extends Type, @primitive {

// 无返回值类型
class VoidType extends Type, @primitive {

// 包装类的类型
class BoxedType extends RefType {

//还有枚举类型、八大类型对应的类型、交叉类型等等

上述这些类型还只是其中的一部分,还有其他一些我都理解不了的类型,就很复杂很复杂。想要学习哪些类型,就只能对着源码看了。

我们在 Field 里就用 getType() 获取了一个属性的类型是什么。

方法 Method

类中除了属性之外,还有一个重要成员就是方法。

class Method extends Callable, @method {

Method 类常用的谓词如下: 这里用 this 代表这个方法的调用者

  • predicate overrides(Method m) {: 判断 this 是否重写 m 方法。
  • Method getAnOverride() {: 获取 this 的所有重写的方法。
  • SrcMethod getASourceOverriddenMethod() {: 获取 this 的源方法。
  • override SrcMethod getSourceDeclaration() { : 获取 this 的原始声明
  • override string getSignature() { : 获取 this 的签名。
  • override MethodCall getAReference() { : 获取对 this 的调用。
  • override predicate isPublic() {: 判断 this 的权限是否是 public。
  • override predicate isAbstract() {: 判断 this 是不是抽象函数。

这些方法也都比较好理解。就不过多的解释了,直接写一些代码帮助理解。

import java

// 我这里查询了一下 csrf 类中,有没有一些被重写的方法。
from Class c1, Class c2, Method m1, Method m2, Class c3, Method m3
where c1.getAMethod() = m1 and c2.getAMethod() = m2 and m1.overrides(m2) and
      c1.getPackage().getName().indexOf("org.owasp.webgoat.lessons.csrf") = 0 and
      // 查询一下  ClientSideFilteringAssignment 里的方法的签名
      c3.getName() = "ClientSideFilteringAssignment" and c3.getAMethod() = m3 
select c1, m1, c2, m2, m1.getASourceOverriddenMethod(), m3.getSignature()

// 其他例子
from Class c1, Method m1
where c1.getName() = "Lesson" and c1.getAMethod() = m1 and m1.getName() = "getId"
select m1.getAReference()

image.png

graph BT
Method --继承--> method["@method"] & Callable
Callable --继承--> callable["@callable"] & StmtParent & Member
StmtParent --继承--> stmtparent["@stmtparent"]
StmtParent --继承----> Top

Member --继承--> member["@member"]
Member --继承--> Annotatable & Modifiable  --继承--> Element
Element --继承--> Top & element["@element"]
Top --继承--> top["@top"]

可调用 Callable 和 调用 Call

定义一个方法的时候,除了方法名 还需要定义方法的返回值、方法的修饰符、方法的参数、方法的语句、方法的返回等等。那么我们就来看看如何查询这些东西吧。

我们这里简单获取一个函数的参数。

from Class c1, Method m1
where c1.hasQualifiedName("org.owasp.webgoat.lessons.csrf", "CSRFLogin") and 
      c1.getAMethod() = m1 and m1.getName() = "completed"
select m1.getAParameter()

查看 getAParameter() 的源码发现,这个方法继承自 Callable 类。

java 中可调用的东西,可不止函数,还有其他的东西,这里为了说明方便,就只用函数代替了。

// 获取方法的描述
string getMethodDescriptor() {

// 获取函数的返回值
Type getReturnType() {

// 获取方法的参数
Parameter getAParameter() {

// 获取被 this 所使用的函数
Callable getACallee() {

// 获取 this 被那个函数调用了
Call getAReference() { 

// 获取函数的方法体
BlockStmt getBody() {

注意这里的 getACallee 的意思是一个函数的方法体里面调用了一些函数,那么这些被调用的函数就会被 getACallee 返回。

Callable 里面并没有获取一个方法的修饰器的方法,而获取方法的修饰器的方法在 Modifiable 类中。

import java

from Class c1, Method m1, Callable ca1, MethodCall call1
where c1.hasQualifiedName("org.owasp.webgoat.lessons.xxe", "CommentsCache") and 
      c1.getAMethod() = m1 and m1.getName() = "reset" and
      m1.getACallee() = ca1 and m1 = call1.getEnclosingCallable()
select call1
  • override Callable getEnclosingCallable() { : 返回 this 的最直接的调用者

对于 comments.clear(); 这一个语句来说,最直接的使用者就是 reset() 这个方法。

image.png

  • Callable getCaller() {: 返回被使用的函数的使用者。在上面的例子中,对于 comments.clear(); 这一次调用来说使用者是 reset() 这个函数。
public void reset(WebGoatUser user) {
    comments.clear();
    userComments.remove(user);
    initDefaultComments();
}

这是一段 java 代码,在 QL 看来:

对于这个 reset() 函数来说,clear()、remove()、initDefaultComments()reset()Callee

对于 clear()、remove()、initDefaultComments() 来说,reset() 是它的 Caller

解释 CalleeCaller 的时候完全不涉及 comments ,因为当我们讨论 comments.clear() 的时候是使用 调用表达式语句 相关的概念去描述的。

这里我更愿意将 Callee 翻译为被使用者, Caller 翻译为 使用者。

在后面讲调用表达式语句的时候(也就是 comments.clear();),我觉得才应该翻译为调用,可惜目前没有官方的翻译,机翻让我的学习过程雪上加霜。😩

接下来我们关注 userComments.remove(user); 这一次函数调用。

import java

from Class c1, Method m1, Callable ca1, MethodCall call1
where c1.hasQualifiedName("org.owasp.webgoat.lessons.xxe", "CommentsCache") and 
      c1.getAMethod() = m1 and m1.getName() = "reset" and
      m1.getACallee() = ca1 and m1 = call1.getEnclosingCallable() and call1.getMethod().getName() = "remove"
select call1.getAnArgument()

这里我通过一步一步的限定条件,直到锁定到我想要的东西。

  • override Expr getAnArgument() {: 获取方法调用中传入的参数

image.png

表达式 Expr

在介绍 Method 的时候,我画出了 Method 的继承关系图,里面涉及到了 Callable 的继承关系图,从图中可以看出, Callable 和 Call 其实没有关系。

确实是这样的。

Callable 的概念是指可以调用的东西,比如函数、构造函数等等。 Call 是指对某个可调用的东西的调用行为,包括使用、调用等等。

上一节我用了一个例子来区分使用和调用,因为我自己也迷糊了半天,这里再强调一遍吧,免得自己忘记了。

public void reset(WebGoatUser user) {
  comments.clear();
  userComments.remove(user);
  initDefaultComments();
}

上面这是一段 java 代码,现在我要定义一些研究对象:

  • reset() 函数。
  • clear() 函数。
  • comments.clear(); 一句 java 代码。

然后我们描述一下上面的代码:

  • reset() 函数在方法体中 Call(使用) 了 clear() 函数。这是 Call 的一种,我更愿意称之为使用。
  • 在这句表达式中 comments.clear(); 我们 Call(调用) 了 clear() 函数。这里的调用其实和 reset() 函数是无关的了,我们这里只研究这句代码本身,在 QL 里,是用 Expr 也就是表达式来定义这种具体的代码。

因为上面 comments.clear(); 举的这个例子是调用一个方法,也就是 MethodCall,那么我们来画一下 MethodCall 的继承图。

graph BT
MethodCall --继承--> methodaccess["@methodaccess"]  & Expr & Call
Expr --继承--> expr["@expr"] & ExprParent
ExprParent --继承--> exprparent["@exprparent"]
ExprParent --继承--> Top

Call --继承--> ExprParent
Call --继承--> caller["@caller"]

Top --继承--> top["@top"]

除了调用方法表达式之外还有很多的其他表达式,比如:ErrorExpr(错误表达式)、ArrayAccess(数组访问表达式)、ArrayCreationExpr(数组创建表达式)、MulExpr(乘法表达式) 等等,差不多有 100 多种表达式。

简单举个例子吧:

import java

from ArrayCreationExpr ace1
where ace1.getFirstDimensionSize() = 2 
and ace1.getType().getName() = "String[]"
and ace1.getIndex() = 1
and ace1.getEnclosingCallable().getName() = "generateToken"
select ace1, ace1.getEnclosingCallable()
  • getFirstDimensionSize(): 这个数组创建表达式所创建的数组的第一个纬度的长度是多少
  • getType(): 这个数组创建表达式所创建的数组的类型是什么
  • getIndex(): 这个数组创建表达式对于它的父表达式来说是第几个表达式。
  • getEnclosingCallable(): 这个数组创建表达式所属的 Callable 是谁(如果有的话)。

可以看到非常准确的找到了我想要找的这个数组创建表达式:

image.png

当然了最常用的还是 MethodCall, 我们举个例子

import java

from MethodCall mc1
where mc1.getMethod().getName() = "validate"
and mc1.getEnclosingCallable().getName() = "registration"
and mc1.getArgument(0).getType().getName() = "UserForm"
and mc1.getArgument(1).getType().getName() = "BindingResult"
select mc1, mc1.getEnclosingStmt()

上面的代码查询的就是下面的语句:

image.png

  • getMethod(): 当前函数调用所调用的函数是谁。
  • getEnclosingCallable(): 当前函数调用所属的 Callable 是谁。
  • getArgument(): 获取第几个参数。
  • getEnclosingStmt(): 获取当前函数调用被那个语句包裹。

语句 Stmt

上面我们讲表达式的时候提到了语句,例如 .validate( 这个函数调用就是被包裹在 userValidator.validate(userForm, bindingResult); 这个语句中的。

学习 java 的时候,我们当时就学过,java 中有 if 语句、for 循环语句、return 语句等等等等。

所以 QL 中当然也有对这些语句进行查询的类,那就是 Stmt 类

graph BT
IfStmt --继承--> ConditionalStmt & ifstmt["@ifstmt"]
ConditionalStmt --继承--> Stmt


Stmt --继承--> stmt["@stmt"]  & ExprParent & StmtParent
ExprParent --继承--> exprparent["@exprparent"]
ExprParent --继承--> Top

StmtParent --继承--> stmtparent["@stmtparent"] & Top

Top --继承--> top["@top"]

上图简单画了一下 IfStmt 的继承关系图。

下面简单介绍一下 IfStmt 的用法。

import java

from IfStmt is1
where is1.getEnclosingCallable().getName() = "newToken"
and not exists(is1.getElse())
and is1.getCondition().getAPrimaryQlClass() = "OrLogicalExpr"
select is1,is1.getAChild()

可以非常轻松的查到我想查到的类: image.png

  • getEnclosingCallable() 获取当前的 IfStmt 被那个 Callable 包裹。
  • getElse() 获取当前的 IfStmt 的 else 语句。
  • getCondition() 获取当前的 IfStmt 括号里的表达式。
  • getAPrimaryQlClass() 获取当前 Expr 的最直接的 QL 类。
  • getAChild() 获取子 Stmt 。

Ok,当然还有其他一些内容,不过就交给大家自己去探索了。

照葫芦画瓢

官方标准库文档:Index (github.com)

学了这么些也只是会了 CodeQL 的皮毛而已,最重要的我们要学会看官方的文档,我这里以 MethodCall 这个最基本的场景来说明吧。

image.png

查看 MehtodCall 类,可以得知其父类、间接父类、子类、谓词、继承的谓词等等。

那么我们就以 MethodCall 来举例子学习这些谓词:

假设我们现在研究下面一段代码, 主要是研究这句方法调用语句:

protected AttackResult injectableQuery(String query) {
    ResultSet results = statement.executeQuery(query);

然后我们用 CodeQL 查询这个方法调用:

import java

from MethodCall methodcall, Method method
where method.getDeclaringType().hasQualifiedName("org.owasp.webgoat.lessons.sqlinjection.introduction", "SqlInjectionLesson2")
and methodcall.getEnclosingCallable() = method
and methodcall.getMethod().hasName("executeQuery")
select methodcall

那么每个谓词查询出来都是什么呢?

flowchart LR
    subgraph QL代码
        a1["methodcall"]
        a2["methodcall.getAPrimaryQlClass()"]
        a3["methodcall.getATypeArgument()"]
        a4["methodcall.getAnArgument()"]
        a5["methodcall.getArgument(0)"]
        a6["methodcall.getEnclosingCallable()"]
        a7["methodcall.getEnclosingStmt()"]
        a8["methodcall.getMethod()"]
        a9["methodcall.getQualifier()"]
        a10["methodcall.getReceiverType()"]
        a11["methodcall.getTypeArgument(0)"]
        a12["methodcall.printAccess()"]
        a13["methodcall.toString()"]
    end
    subgraph java代码
        b1["statement.executeQuery(query)"]
        b2["MethodCall"]
        b3["无,调用时没有指定参数类型。"]
        b4["query"]
        b5["query"]
        b6["injectableQuery"]
        b7["ResultSet results = statement.executeQuery(query);"]
        b8["executeQuery"]
        b9["statement"]
        b10["Statement"]
        b11["无,调用时没有指定参数类型。"]
        b12["executeQuery(...)"]
        b13["executeQuery(...)"]
    end
a1 --> b1
a2 --> b2
a3 --> b3
a4 --> b4
a5 --> b5
a6 --> b6
a7 --> b7
a8 --> b8
a9 --> b9
a10 --> b10
a11 --> b11
a12 --> b12
a13 --> b13

还有几个不常用的,我也不太懂是啥。

大概就是这样,具体的就自己去看了。