Codeql审计实战
参考:https://www.synacktiv.com/publications/finding-gadgets-like-its-2022
对应的git项目地址:https://github.com/synacktiv/QLinspector
首先先是从issue学到点东西,对于给jar包的codeql审计,首先要进行反编译
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
|
实操
找反序列化链子的source一般就是readObject,然后sink一般是Runtime.exec这种的
查找sink
我们先定义找sink的函数
1
2
3
4
5
|
private class RuntimeExec extends Method {
RuntimeExec(){
hasQualifiedName("java.lang", "Runtime", "exec")
}
}
|
接下来我们定义了一个包含很多常见危险类的方法的sink,这里可以根据需要继续添加类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class DangerousMethod extends Callable {
DangerousMethod(){
this instanceof ExpressionEvaluationMethod or
this instanceof ReflectionInvocationMethod or
this instanceof RuntimeExec or
this instanceof URL or
this instanceof ProcessBuilder or
this instanceof Files or
this instanceof FileInputStream or
this instanceof FileOutputStream or
this instanceof EvalScriptEngine or
this instanceof ClassLoader or
this instanceof ContextLookup
}
}
|
文章中是用MethodAccess方法,来搜索对exec方法的调用,前面我们说过新版换成MethodCall了
1
2
3
|
from MethodCall ma
where ma.getMethod() instanceof RuntimeExec
select ma
|
这里要用作者的项目创建数据库的话jdk要是jdk11+,我用的是jdk17
1
|
codeql database create qlinspector --language=java
|

然后在vscode同样的操作,这里我们结合上面的写一个ql
1
2
3
4
5
6
7
8
9
10
11
|
import java
private class RuntimeExec extends Method {
RuntimeExec(){
this.getDeclaringType().hasQualifiedName("java.lang", "Runtime") and
this.hasName("exec")
}
}
from MethodCall ma
where ma.getMethod() instanceof RuntimeExec
select ma
|

接下来我们想要查找所有调用危险方法的函数,所以加上前面我们定义的危险函数类,输出所有调用危险方法的函数
1
2
3
4
5
6
7
|
private class CallsDangerousMethod extends Callable {
CallsDangerousMethod(){
exists(MethodAccess ma | ma.getMethod() instanceof DangerousMethod and ma.getEnclosingCallable() = this)
}
}
|
但是直接引用的话,由于codeql本身并没有这些类,我们需要手动定义,这里让gemini完善了一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
import java
// =======================================================
// 1. 手动定义缺失的 Dangerous 类
// 这些类在标准库中不存在,必须自己写规则来匹配 Java 的 API
// =======================================================
// 匹配 java.lang.Runtime.exec
class RuntimeExec extends Method {
RuntimeExec() {
this.getDeclaringType().hasQualifiedName("java.lang", "Runtime") and
this.hasName("exec")
}
}
// 匹配 ProcessBuilder 的构造函数或 start 方法
class ProcessBuilder extends Callable {
ProcessBuilder() {
this.getDeclaringType().hasQualifiedName("java.lang", "ProcessBuilder") and
(this instanceof Constructor or this.hasName("start"))
}
}
// 匹配反射调用 Method.invoke
class ReflectionInvocationMethod extends Method {
ReflectionInvocationMethod() {
this.getDeclaringType().hasQualifiedName("java.lang.reflect", "Method") and
this.hasName("invoke")
}
}
// 匹配 URL 的构造函数 (SSRF 风险)
class URL extends Constructor {
URL() {
this.getDeclaringType().hasQualifiedName("java.net", "URL")
}
}
// 匹配文件读取 FileInputStream
class FileInputStream extends Constructor {
FileInputStream() {
this.getDeclaringType().hasQualifiedName("java.io", "FileInputStream")
}
}
// 匹配文件写入 FileOutputStream
class FileOutputStream extends Constructor {
FileOutputStream() {
this.getDeclaringType().hasQualifiedName("java.io", "FileOutputStream")
}
}
// 匹配 java.nio.file.Files 的所有方法 (如 read, write, copy)
class Files extends Method {
Files() {
this.getDeclaringType().hasQualifiedName("java.nio.file", "Files")
}
}
// 匹配脚本引擎执行 (Code Injection)
class ExpressionEvaluationMethod extends Method {
ExpressionEvaluationMethod() {
this.hasName("eval") and
this.getDeclaringType().getAnAncestor().hasQualifiedName("javax.script", "ScriptEngine")
}
}
// 匹配 JNDI 查询 (Context.lookup)
class ContextLookup extends Method {
ContextLookup() {
this.hasName("lookup") and
this.getDeclaringType().getAnAncestor().hasQualifiedName("javax.naming", "Context")
}
}
// 匹配 ClassLoader (可能导致类加载风险)
class ClassLoader extends Method {
ClassLoader() {
(this.hasName("loadClass") or this.hasName("defineClass")) and
this.getDeclaringType().getAnAncestor().hasQualifiedName("java.lang", "ClassLoader")
}
}
// =======================================================
// 2. 你的核心逻辑
// =======================================================
class DangerousMethod extends Callable {
DangerousMethod(){
this instanceof ExpressionEvaluationMethod or
this instanceof ReflectionInvocationMethod or
this instanceof RuntimeExec or
this instanceof URL or
this instanceof ProcessBuilder or
this instanceof Files or
this instanceof FileInputStream or
this instanceof FileOutputStream or
this instanceof ClassLoader or
this instanceof ContextLookup
}
}
private class CallsDangerousMethod extends Callable {
CallsDangerousMethod(){
// 查找当前方法内部是否调用了上述危险方法
exists(MethodCall ma |
ma.getMethod() instanceof DangerousMethod and
ma.getEnclosingCallable() = this
)
}
}
from Callable c
where c instanceof CallsDangerousMethod
select c
|

查找source
除了readObject,我们可以考虑调用任意getter的函数,比如hashCode, equals, compare等等
所以我们可以定义source的ql如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class Source extends Callable{
Source(){
getDeclaringType().getASupertype*() instanceof TypeSerializable and (
this instanceof MapSource or
this instanceof SerializableMethods or
this instanceof Equals or
this instanceof HashCode or
this instanceof Compare or
this instanceof ExternalizableMethod or
this instanceof ObjectInputValidationMethod or
this instanceof InvocationHandlerMethod or
this instanceof MethodHandlerMethod or
this instanceof GroovyMethod
)
}
}
|
我们要找的这些必须继承Serializable,所以我们得到下面的ql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
import java
// ==========================================
// 1. 补全缺失的 Gadget 识别逻辑
// (这些类在标准库中不存在,必须手动定义)
// ==========================================
// 1. 序列化相关方法 (readObject 等)
class SerializableMethods extends Method {
SerializableMethods() {
(this.hasName("readObject") and this.getNumberOfParameters() = 1 and this.getParameter(0).getType().(RefType).hasQualifiedName("java.io", "ObjectInputStream")) or
(this.hasName("readResolve") and this.getNumberOfParameters() = 0) or
(this.hasName("writeReplace") and this.getNumberOfParameters() = 0) or
(this.hasName("readObjectNoData") and this.getNumberOfParameters() = 0) or
(this.hasName("finalize") and this.getNumberOfParameters() = 0)
}
}
// 2. Map 接口相关方法 (常用于 LazyMap 利用链)
class MapSource extends Method {
MapSource() {
this.getDeclaringType().getASupertype*().hasQualifiedName("java.util", "Map") and
(
this.hasName("get") or
this.hasName("put") or
this.hasName("entrySet") or
this.hasName("hashCode")
)
}
}
// 3. Object.equals()
class Equals extends Method {
Equals() {
this.hasName("equals") and
this.getNumberOfParameters() = 1 and
this.getParameter(0).getType() instanceof TypeObject
}
}
// 4. Object.hashCode()
class HashCode extends Method {
HashCode() {
this.hasName("hashCode") and this.getNumberOfParameters() = 0
}
}
// 5. Comparator 或 Comparable
class Compare extends Method {
Compare() {
(this.hasName("compare") and this.getDeclaringType().getASupertype*().hasQualifiedName("java.util", "Comparator")) or
(this.hasName("compareTo") and this.getDeclaringType().getASupertype*().hasQualifiedName("java.lang", "Comparable"))
}
}
// 6. Externalizable 接口
class ExternalizableMethod extends Method {
ExternalizableMethod() {
this.getDeclaringType().getASupertype*().hasQualifiedName("java.io", "Externalizable") and
(this.hasName("readExternal") or this.hasName("writeExternal"))
}
}
// 7. ObjectInputValidation
class ObjectInputValidationMethod extends Method {
ObjectInputValidationMethod() {
this.hasName("validateObject") and
this.getDeclaringType().getASupertype*().hasQualifiedName("java.io", "ObjectInputValidation")
}
}
// 8. 动态代理 InvocationHandler
class InvocationHandlerMethod extends Method {
InvocationHandlerMethod() {
this.hasName("invoke") and
this.getDeclaringType().getASupertype*().hasQualifiedName("java.lang.reflect", "InvocationHandler")
}
}
// 9. Javassist MethodHandler (Hibernate 利用链常见)
class MethodHandlerMethod extends Method {
MethodHandlerMethod() {
this.hasName("invoke") and
this.getDeclaringType().getASupertype*().hasQualifiedName("javassist.util.proxy", "MethodHandler")
}
}
// 10. Groovy 方法 (Groovy 利用链常见)
class GroovyMethod extends Method {
GroovyMethod() {
this.getDeclaringType().getASupertype*().hasQualifiedName("groovy.lang", "GroovyObject") and
(this.hasName("invokeMethod") or this.hasName("getProperty"))
}
}
// ==========================================
// 2. 你的核心 Source 类
// ==========================================
class Source extends Callable {
Source() {
// 使用标准库中的 TypeSerializable
this.getDeclaringType().getASupertype*() instanceof TypeSerializable and (
this instanceof MapSource or
this instanceof SerializableMethods or
this instanceof Equals or
this instanceof HashCode or
this instanceof Compare or
this instanceof ExternalizableMethod or
this instanceof ObjectInputValidationMethod or
this instanceof InvocationHandlerMethod or
this instanceof MethodHandlerMethod or
this instanceof GroovyMethod
)
}
}
from Callable c
where c instanceof Source
select c
|
不过这样会查询到很多重复的,我们需要去重
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
import java
// ==========================================
// 1. 辅助类定义 (Gadget 匹配逻辑)
// ==========================================
// 1. 序列化相关方法
class SerializableMethods extends Method {
SerializableMethods() {
(
this.hasName("readObject") and
this.getNumberOfParameters() = 1 and
this.getParameter(0).getType().(RefType).hasQualifiedName("java.io", "ObjectInputStream")
) or
(this.hasName("readResolve") and this.getNumberOfParameters() = 0) or
(this.hasName("writeReplace") and this.getNumberOfParameters() = 0) or
(this.hasName("readObjectNoData") and this.getNumberOfParameters() = 0) or
(this.hasName("finalize") and this.getNumberOfParameters() = 0)
}
}
// 2. Map 接口相关方法
class MapSource extends Method {
MapSource() {
this.getDeclaringType().getASupertype*().hasQualifiedName("java.util", "Map") and
(this.hasName("get") or this.hasName("put") or this.hasName("entrySet") or this.hasName("hashCode"))
}
}
// 3. Object.equals()
class Equals extends Method {
Equals() {
this.hasName("equals") and
this.getNumberOfParameters() = 1 and
this.getParameter(0).getType() instanceof TypeObject
}
}
// 4. Object.hashCode()
class HashCode extends Method {
HashCode() {
this.hasName("hashCode") and this.getNumberOfParameters() = 0
}
}
// 5. Comparator / Comparable
class Compare extends Method {
Compare() {
(this.hasName("compare") and this.getDeclaringType().getASupertype*().hasQualifiedName("java.util", "Comparator")) or
(this.hasName("compareTo") and this.getDeclaringType().getASupertype*().hasQualifiedName("java.lang", "Comparable"))
}
}
// 6. Externalizable
class ExternalizableMethod extends Method {
ExternalizableMethod() {
this.getDeclaringType().getASupertype*().hasQualifiedName("java.io", "Externalizable") and
(this.hasName("readExternal") or this.hasName("writeExternal"))
}
}
// 7. ObjectInputValidation
class ObjectInputValidationMethod extends Method {
ObjectInputValidationMethod() {
this.hasName("validateObject") and
this.getDeclaringType().getASupertype*().hasQualifiedName("java.io", "ObjectInputValidation")
}
}
// 8. InvocationHandler
class InvocationHandlerMethod extends Method {
InvocationHandlerMethod() {
this.hasName("invoke") and
this.getDeclaringType().getASupertype*().hasQualifiedName("java.lang.reflect", "InvocationHandler")
}
}
// 9. MethodHandler
class MethodHandlerMethod extends Method {
MethodHandlerMethod() {
this.hasName("invoke") and
this.getDeclaringType().getASupertype*().hasQualifiedName("javassist.util.proxy", "MethodHandler")
}
}
// 10. Groovy
class GroovyMethod extends Method {
GroovyMethod() {
this.getDeclaringType().getASupertype*().hasQualifiedName("groovy.lang", "GroovyObject") and
(this.hasName("invokeMethod") or this.hasName("getProperty"))
}
}
// ==========================================
// 2. 核心 Source 定义 (已去重)
// ==========================================
class Source extends Callable {
Source() {
// [关键修改] 过滤掉泛型实例化产生的副本,只保留源代码中的定义
this.getSourceDeclaration() = this
and
// 确保类是可序列化的
this.getDeclaringType().getASupertype*() instanceof TypeSerializable
and
(
this instanceof MapSource or
this instanceof SerializableMethods or
this instanceof Equals or
this instanceof HashCode or
this instanceof Compare or
this instanceof ExternalizableMethod or
this instanceof ObjectInputValidationMethod or
this instanceof InvocationHandlerMethod or
this instanceof MethodHandlerMethod or
this instanceof GroovyMethod
)
}
}
// 使用 distinct 确保结果行唯一
from Callable c
where c instanceof Source
select c, c.getDeclaringType().toString()
|
这里会把jdk原生的也查询进来
查找source到sink的gadget
这个项目都没几步,查询只有一步,就不写了,差不多跟上一篇一样
1
2
3
4
5
|
from Callable source, Callable sink
where source instanceof Source and
sink instanceof CallsDangerousMethod and
source.polyCalls(sink)
select source, sink
|
从旧链到挖掘新链
我们可以下载yso里面记录的链子的java库来验证我们的查询
首先要找到sink,这里作者没给项目,下面基本照搬作者原话,但是他的codeql版本很老,新版很多语法对不上
找gadget的ql,这里写的是默认source到sink中间0个调用,显然是不太可能
1
2
3
4
5
6
7
8
9
|
from Callable c0, MethodAccess ma
where c0 instanceof RecursiveCallToDangerousMethod and
ma.getMethod() instanceof DangerousMethod and
ma.getEnclosingCallable() = c0 and
c0 instanceof Source
select c0, ma
|
为了找到source到sink的完整gadget,需要添加中间调用,找到完整的链式调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
from Callable c0, Callable c1, Callable c2, Callable c3, Callable c4,
MethodAccess ma
where c0 instanceof RecursiveCallToDangerousMethod and
ma.getMethod() instanceof DangerousMethod and
ma.getEnclosingCallable() = c0 and
c1.polyCalls(c0) and
c1 instanceof RecursiveCallToDangerousMethod and
c2.polyCalls(c1) and
c2 instanceof RecursiveCallToDangerousMethod and
c3.polyCalls(c2) and
c3 instanceof RecursiveCallToDangerousMethod and
c4.polyCalls(c3) and
c4 instanceof RecursiveCallToDangerousMethod and
c4 instanceof Source
select c4, c3, c2, c1, c0, ma
|
也就是利用这个思路,我们可以在旧版本链子的基础上去java那些库里面寻找别的gadget实现相同的效果
原作者也是通过这个挖到很多新链子

