Featured image of post Codeql审计实战

Codeql审计实战

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

image-20251219000630310

然后在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

image-20251219002132107

接下来我们想要查找所有调用危险方法的函数,所以加上前面我们定义的危险函数类,输出所有调用危险方法的函数

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

image-20251220210946181

查找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实现相同的效果

原作者也是通过这个挖到很多新链子

image-20251230210218127

image-20251230210235155

使用 Hugo 构建
主题 StackJimmy 设计