codeql实战练习-micro_service_seclab
字数 1001 2025-08-22 12:23:12
CodeQL实战:Java微服务安全漏洞检测
1. CodeQL基础与数据库构建
1.1 数据库构建命令
构建Java项目的CodeQL数据库:
codeql database create ~/xxxxxx/micro-service-seclab-database \
--language="java" \
--command="mvn clean package -Dmaven.test.skip=true" \
--source-root=./micro-service-seclab/
1.2 核心概念
- RemoteFlowSource: 远程用户输入的数据流源
- TaintTracking::Configuration: 污点跟踪配置基类
- DataFlow::PathGraph: 数据流路径图
2. SQL注入检测
2.1 基础SQL注入检测
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.QueryInjection
import DataFlow::PathGraph
class VulConfig extends TaintTracking::Configuration {
VulConfig() { this = "SqlInjectionConfig" }
override predicate isSource(DataFlow::Node src) {
src instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodAccess call |
method.hasName("query") and
call.getMethod() = method and
sink.asExpr() = call.getArgument(0)
)
}
}
from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"
2.2 误报处理
对于List<Long>、List<Integer>等类型会产生误报,需要添加isSanitizer:
override predicate isSanitizer(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
)
}
类型系统说明:
- PrimitiveType: boolean, byte, short, char, int, long, float, double
- BoxedType: Boolean, Byte, Short, Character, Integer, Long, Float, Double
- NumberType: java.lang.Number的子类型
- ParameterizedType: 泛型类的实例化,如List
2.3 漏报处理 - Optional类型
处理Optional<String>类型的漏报:
predicate isTaintedString(Expr expSrc, Expr expDest) {
exists(Method method, MethodAccess call, MethodAccess call1 |
expSrc = call1.getArgument(0) and
expDest = call and
call.getMethod() = method and
method.hasName("get") and
method.getDeclaringType().toString() = "Optional<String>" and
call1.getArgument(0).getType().toString() = "Optional<String>"
)
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
isTaintedString(node1.asExpr(), node2.asExpr())
}
3. Fastjson反序列化检测
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.QueryInjection
import DataFlow::PathGraph
class FastjsonVulConfig extends TaintTracking::Configuration {
FastjsonVulConfig() { this = "fastjson" }
override predicate isSource(DataFlow::Node src) {
src instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodAccess call |
method.hasName("parseObject") and
call.getMethod() = method and
sink.asExpr() = call.getArgument(0)
)
}
}
from FastjsonVulConfig fastjsonVul, DataFlow::PathNode source, DataFlow::PathNode sink
where fastjsonVul.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"
4. SSRF检测
4.1 基础SSRF检测
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.QueryInjection
import DataFlow::PathGraph
import semmle.code.java.security.RequestForgeryConfig
class SSRFVulConfig extends TaintTracking::Configuration {
SSRFVulConfig() { this = "SSRFVulConfig" }
override predicate isSource(DataFlow::Node src) {
src instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node sink) {
sink instanceof RequestForgerySink
}
}
from SSRFVulConfig ssrfVulConfig, DataFlow::PathNode source, DataFlow::PathNode sink
where ssrfVulConfig.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"
4.2 漏报处理1 - String.valueOf(URL)
方法1:修改RequestForgery.qll源码,添加对String.valueOf(URL)的处理:
class TypeStringLib extends RefType {
TypeStringLib() { this.hasQualifiedName("java.lang", "String") }
}
class StringValue extends MethodAccess {
StringValue() {
this.getCallee().getDeclaringType() instanceof TypeStringLib and
this.getCallee().hasName("valueOf")
}
}
private class DefaultRequestForgeryAdditionalTaintStep extends RequestForgeryAdditionalTaintStep {
override predicate propagatesTaint(DataFlow::Node pred, DataFlow::Node succ) {
// 原有逻辑...
or
// 处理String.valueOf(URL)
exists(StringValue c |
c.getArgument(0) = pred.asExpr() |
succ.asExpr() = c
)
}
}
方法2:不改写lib,直接改写QL查询:
class TypeStringLib extends RefType {
TypeStringLib() { this.hasQualifiedName("java.lang", "String") }
}
class StringValue extends MethodAccess {
StringValue() {
this.getCallee().getDeclaringType() instanceof TypeStringLib and
this.getCallee().hasName("valueOf")
}
}
private class MyRequestForgeryAdditionalTaintStep extends RequestForgeryAdditionalTaintStep {
override predicate propagatesTaint(DataFlow::Node pred, DataFlow::Node succ) {
exists(UriCreation c | c.getHostArg() = pred.asExpr() | succ.asExpr() = c) or
exists(UrlConstructorCall c | c.getHostArg() = pred.asExpr() | succ.asExpr() = c) or
exists(StringValue c | c.getArgument(0) = pred.asExpr() | succ.asExpr() = c)
}
}
4.3 漏报处理2 - OkHttpClient
处理OkHttpClient的链式调用:
MethodAccess url(MethodAccess ma, DataFlow::Node node) {
exists(MethodAccess mc |
mc = ma.getAChildExpr() |
if mc.getCallee().hasName("url") and mc.getArgument(0) = node.asExpr()
then result = mc
else result = url(mc, node)
)
}
MethodAccess m(DataFlow::Node node) {
exists(MethodAccess ma |
ma.getCallee().hasName("build") and
ma.getCallee().getDeclaringType().hasName("Builder") |
result = url(ma, node)
)
}
override predicate isSink(DataFlow::Node sink) {
sink instanceof RequestForgerySink or
exists(m(sink))
}
5. XXE检测
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.ExternalFlow
import DataFlow::PathGraph
class XXEVulConfig extends TaintTracking::Configuration {
XXEVulConfig() { this = "XXEVulConfig" }
override predicate isSource(DataFlow::Node src) {
src instanceof RemoteFlowSource
}
override predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodAccess call |
method.hasName("parse") and
call.getMethod() = method and
sink.asExpr() = call.getArgument(0)
)
}
}
from XXEVulConfig xxeVulConfig, DataFlow::PathNode source, DataFlow::PathNode sink
where xxeVulConfig.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"
6. 最佳实践
- 积极查阅标准库:遇到不熟悉的类或方法时,应积极查阅CodeQL standard libraries
- 多积累记录:建立自己的查询代码库,积累常见漏洞模式
- 误报处理:通过isSanitizer精准过滤非漏洞路径
- 漏报处理:通过isAdditionalTaintStep补充数据流传播规则
- 语法树分析:对于复杂调用链,先分析语法树结构再编写对应查询
通过以上方法,可以构建针对Java微服务应用的全面安全检测方案,覆盖SQL注入、Fastjson反序列化、SSRF、XXE等常见漏洞类型。