CodeQL & SQL注入自动化检测
2026-01-15 17:28:58

前言

在学习CodeQL之前,接触的静态分析工具如Tabby, Java-Analyzer等工具有很多局限性,比如只能分析Java代码,后两者更大的优势是寻找Java反序列化链进行黑名单与WAF绕过,而CodeQL可以对不同的编程语言如Java, Go等进行静态分析,代码式的查询语句使其更容易编写检测常规漏洞如SQL注入的规则,在SAST工具盛极一时的当下,学习CodeQL就显得很有必要了。

环境搭建

Java示范靶场中使用如下命令建立分析数据库

1
./codeql database create codeqltest --language=java --command="mvn clean package -DskipTests"

image-20260104135808-mhyq3hy

VSCode中打开ql文件夹,然后引入刚刚建立的分析数据库codeqltest

image-20260104141732-tdv4tar

新建example.ql,使用查询语句打印输出Hello world

image-20260104142115-uv0q3fh

CodeQL 语法&规则

Method:方法类,Method method 表示获取当前项目中的所有方法

exists(A | B):存在函数,是否存在A使B成立

1
2
3
4
5
import java

from Method method //获取当前项目中的所有方法
where method.hasName("getStudent") //获取所有名为"getStudent"的方法
select method.getName(), method.getDeclaringType() //打印输出方法名和方法所属类名

image-20260104143111-9jibq3h

根据输出结果可知,IndexDbIndexLogic类中各有一个getStudent方法

image-20260104143317-o690brm

image-20260104143242-jpy1vvx

谓词 - predicate

用于解决where查询条件过长导致逻辑不清晰的问题

1
2
3
4
5
6
7
8
9
10
import java

predicate isStudent(Method method) {
exists(|method.hasName("getStudent"))
}


from Method method
where isStudent(method)
select method.getName(), method.getDeclaringType()

可以理解为自定义了一个函数isStudent作为查询条件

image-20260104144034-dn5zxxe

静态分析

source - 用户可控输入点

sink - 危险函数

sanitizer - 过滤函数,指在整个的漏洞链条当中,如果存在一个方法阻断了整个传递链,那么这个方法就叫sanitizer

以检测SQL注入漏洞为例

首先获取所有的source点,可以直接使用CodeQL SDK提供的RemoteFlowSource

1
2
3
4
5
6
7
8
9
import java
import semmle.code.java.dataflow.FlowSources


predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }

from DataFlow::Node src
where isSource(src)
select "Source: ", src

image-20260104155439-m7lrys7

找到Source之后,接下来就是确定sink,这个案例中,可以通过查找query方法来判断SQL注入,可以将所有传给名为query的方法的第一个参数为sink

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java
import semmle.code.java.dataflow.FlowSources


predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodCall call |
method.hasName("query")
and
call.getMethod() = method and
sink.asExpr() = call.getArgument(0)
)
}

from DataFlow::Node src
where isSink(src)
select "Sink: ", src

image-20260104165656-blkoemi

确定sourcesink后,最后需要输出 source --> sink的形式

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
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.DataFlow


module SQLINJ_Source_To_sink_Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource
}

predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodCall call |
method.hasName("query")
and
call.getMethod() = method and
sink.asExpr() = call.getArgument(0)
)
}
}

module SQLINJ_Source_To_sink = TaintTracking::Global<SQLINJ_Source_To_sink_Config>;

from DataFlow::Node source, DataFlow::Node sink
where SQLINJ_Source_To_sink::flow(source, sink)
select source, "-->", sink

image-20260104172341-ie6hjnx

效果尚可

image-20260104172852-xiba4sh

误报过滤

但是一般的source --> sink会不可避免地产生误报,如下图所示,虽然用户可控输入被直接拼接至SQL查询语句中,但是代码中强制要求输入参数类型为Long,无法造成SQL注入,产生误报

image-20260104225429-0wn1oa7

此时就需要设计一个sanitizer来减少误报,对传入参数的类型进行判断,不能是基本数据类型PrimitiveTypeBoxedType、数字类型NumverType以及泛型数字类型如此处的List<Long>

修正后的QL查询代码如下所示,添加了isBarrier方法进行过滤

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
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.DataFlow


module SQLINJ_Source_To_sink_Config implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource
}

predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodCall call |
method.hasName("query")
and
call.getMethod() = method and
sink.asExpr() = call.getArgument(0)
)
}

predicate isBarrier(DataFlow::Node node) {
node.getType() instanceof PrimitiveType
or
node.getType() instanceof BoxedType
or
node.getType() instanceof NumberType
or
exists(ParameterizedType pt | node.getType() = pt
and pt.getTypeArgument(0) instanceof NumberType)
}
}

module SQLINJ_Source_To_sink = TaintTracking::Global<SQLINJ_Source_To_sink_Config>;

from DataFlow::Node source, DataFlow::Node sink
where SQLINJ_Source_To_sink::flow(source, sink)
select source, "-->", sink

过滤误报后成功检测出五处SQL注入漏洞

image-20260104232152-wlz5daw

上一页
2026-01-15 17:28:58
下一页