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. 最佳实践

  1. 积极查阅标准库:遇到不熟悉的类或方法时,应积极查阅CodeQL standard libraries
  2. 多积累记录:建立自己的查询代码库,积累常见漏洞模式
  3. 误报处理:通过isSanitizer精准过滤非漏洞路径
  4. 漏报处理:通过isAdditionalTaintStep补充数据流传播规则
  5. 语法树分析:对于复杂调用链,先分析语法树结构再编写对应查询

通过以上方法,可以构建针对Java微服务应用的全面安全检测方案,覆盖SQL注入、Fastjson反序列化、SSRF、XXE等常见漏洞类型。

CodeQL实战:Java微服务安全漏洞检测 1. CodeQL基础与数据库构建 1.1 数据库构建命令 构建Java项目的CodeQL数据库: 1.2 核心概念 RemoteFlowSource : 远程用户输入的数据流源 TaintTracking::Configuration : 污点跟踪配置基类 DataFlow::PathGraph : 数据流路径图 2. SQL注入检测 2.1 基础SQL注入检测 2.2 误报处理 对于 List<Long> 、 List<Integer> 等类型会产生误报,需要添加 isSanitizer : 类型系统说明: 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> 类型的漏报: 3. Fastjson反序列化检测 4. SSRF检测 4.1 基础SSRF检测 4.2 漏报处理1 - String.valueOf(URL) 方法1:修改RequestForgery.qll源码,添加对String.valueOf(URL)的处理: 方法2:不改写lib,直接改写QL查询: 4.3 漏报处理2 - OkHttpClient 处理OkHttpClient的链式调用: 5. XXE检测 6. 最佳实践 积极查阅标准库 :遇到不熟悉的类或方法时,应积极查阅CodeQL standard libraries 多积累记录 :建立自己的查询代码库,积累常见漏洞模式 误报处理 :通过isSanitizer精准过滤非漏洞路径 漏报处理 :通过isAdditionalTaintStep补充数据流传播规则 语法树分析 :对于复杂调用链,先分析语法树结构再编写对应查询 通过以上方法,可以构建针对Java微服务应用的全面安全检测方案,覆盖SQL注入、Fastjson反序列化、SSRF、XXE等常见漏洞类型。