前面了解了很多 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()
获取类的所有注解。
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。
其实类本身并没有太多的内容,接下来看看类里面的内容。
属性 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()
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()
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() 这个方法。
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
解释 Callee
和 Caller
的时候完全不涉及 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() {
: 获取方法调用中传入的参数
表达式 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
是谁(如果有的话)。
可以看到非常准确的找到了我想要找的这个数组创建表达式:
当然了最常用的还是 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()
上面的代码查询的就是下面的语句:
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()
可以非常轻松的查到我想查到的类:
getEnclosingCallable()
获取当前的 IfStmt 被那个 Callable 包裹。getElse()
获取当前的 IfStmt 的 else 语句。getCondition()
获取当前的 IfStmt 括号里的表达式。getAPrimaryQlClass()
获取当前 Expr 的最直接的 QL 类。getAChild()
获取子 Stmt 。
Ok,当然还有其他一些内容,不过就交给大家自己去探索了。
照葫芦画瓢
官方标准库文档:Index (github.com)
学了这么些也只是会了 CodeQL 的皮毛而已,最重要的我们要学会看官方的文档,我这里以 MethodCall 这个最基本的场景来说明吧。
查看 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
还有几个不常用的,我也不太懂是啥。
大概就是这样,具体的就自己去看了。