本文最后更新于 2026年3月23日 下午
前言
简介
CodeQL 是由 GitHub 开发的一种强大的静态分析引擎,它将代码视为数据,允许安全研究员和开发人员像查询数据库一样来检索代码中的漏洞。
它是现代 DevSecOps 流程中 SAST(静态应用程序安全测试) 的代表性工具,目前广泛应用于 GitHub 的自动代码扫描(Code Scanning)中。
特点
不同于传统SAST使用规则/正则匹配的方式,codeql核心逻辑是将源代码及其语义信息(如控制流、数据流)转化为一个可查询的关系型数据库,再用专用的类似SQL的QL语言来编写查询规则和查询。
并且针对编译型语言和解释型语言,CodeQL 的构建机制有着本质的区别:
- 编译型语言(如C/C++, Java, C#, Go, Kotlin, Swift):
当你执行 codeql database create 并指定 --command(如 mvn clean install)时(必须指定编译命令,这正是编译型语言的特点),CodeQL 会启动一个观察者进程。
注入环境变量:CodeQL 会修改当前 shell 的环境变量(如 PATH、LD_PRELOAD 或特定的编译器hook)。
拦截调用:当你的构建脚本(Maven, Gradle, Make)尝试调用真正的编译器(如 javac, gcc, clang)时,CodeQL 会先“截获”这个请求。
双重动作:
让真正的编译器继续工作,生成可执行文件或字节码(确保构建不报错)。同时运行 CodeQL 提取器(Extractor),解析编译器正在处理的同一份源代码,捕获其完整的语义上下文。
- 解释型语言(如Python, JavaScript, TypeScript, Ruby, PHP):
- 构建原理: 由于不需要编译,CodeQL 直接调用相应的提取器来扫描指定目录下的所有源文件。
- 提取过程: 提取器解析文件系统中的源代码,并将其直接转化为数据库记录。
- 核心要求: 只要源代码文件存在且格式正确即可,不需要任何复杂的环境配置。
核心
codeql的一个核心功能是污点分析。通俗来讲就是追踪“不安全”的数据(如用户输入)是否能在不经过滤的情况下,流向“危险”的操作(如数据库执行、文件写入)。
污点分析可拆解为三个核心概念(3S):
- Source (源头):不可信数据的入口(如 HttpRequest、Intent)。
- Sink (关键操作):执行敏感操作的危险函数(如 SQL 执行语句、Runtime.exec())。
- Sanitizer (过滤器):对数据进行校验、过滤或转义的逻辑(如 HtmlUtils.htmlEscape)。
污点分析本质上是在寻找一条从 Source 到 Sink 的有效路径,且该路径上没有经过有效的 Sanitizer。
链接
支持的语言和框架:https://codeql.github.com/docs/codeql-overview/supported-languages-and-frameworks/

官方文档:https://codeql.github.com/docs/
引擎:https://github.com/github/codeql-cli-binaries/releases
sdk:https://github.com/github/codeql
demo
1 2 3 4 5
| ql-demo/ ├── pom.xml └── src/main/java/com/example/demo/ ├── DemoApplication.java └── DiagnosticController.java
|
pom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>ql-demo</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>
|
DiagnosticController.java
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
| package com.example.demo;
import org.springframework.web.bind.annotation.*; import java.io.*; import java.util.regex.Pattern;
@RestController @RequestMapping("/api") public class DiagnosticController {
/** * 【漏洞接口】 * 没有任何过滤,直接拼接命令。 * 攻击示例:target=127.0.0.1 && whoami */ @GetMapping("/vulnerable/ping") public String insecurePing(@RequestParam String target) throws IOException { String command = "ping -c 1 " + target; return executeCommand(command); }
/** * 【安全接口】 * 增加了正则校验。CodeQL 默认可能仍报错,除非在 QL 脚本中定义其为 Sanitizer。 */ @GetMapping("/safe/ping") public String safePing(@RequestParam String target) throws IOException { if (!isValidIp(target)) { return "Invalid Input"; } String command = "ping -c 1 " + target; return executeCommand(command); }
// 逻辑过滤函数 private boolean isValidIp(String ip) { return Pattern.matches("^(\\d{1,3}\\.){3}\\d{1,3}$", ip); }
// 辅助执行函数 (Sink 所在地) private String executeCommand(String cmd) throws IOException { Process p = Runtime.getRuntime().exec(cmd); // CodeQL 追踪的终点 BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream())); return r.readLine(); } }
|
构建codeql数据库
1
| codeql database create java-rce-db --language=java --command="mvn clean compile"
|


codeql的内置规则是基于CWE框架写的。命令执行相关的内置规则在CWE-078

两处命令执行都被检测到了,并且标注了source和sink

虽然isValidIp()做了较安全的正则过滤,但是内置脚本无法识别到这个Sanitizer。这是因为这是自定义的过滤方法而非标准库或者业界公认的安全库方法(如Integer.parseInt()类型转换、HTML 转义库等)。为了减少漏报的情况,内置规则审计的原则是“疑罪从有”,即不会识别到方法名叫 check、validate等的方法就当作是Sanitizer而不报告这条攻击路径,因为它并不知道这些自定义方法是否真的能保障安全。
当发生上述情况的时候,可以通过手动设置Sanitizer来清除自定义过滤方法产生的误报。

这里是isBarrier方法,官方库好像没有isSanitizer
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
| /** * @name Command Injection * @kind path-problem // 关键:声明这是一个路径问题,允许 IDE 渲染从 Source 到 Sink 的跳转路径 * @problem.severity error // 严重程度:在扫描报告中标记为“错误” * @id java/modern-command-injection */
import java import semmle.code.java.dataflow.TaintTracking // 导入污点追踪库,支持跨方法的变量传递分析 import semmle.code.java.dataflow.FlowSources // 导入预定义的攻击源库(如 Spring/Servlet 入口)
/** * 模块化定义配置(Modern API) * 使用 ConfigSig 签名确保配置符合最新版标准 */ module CommandInjectionConfig implements DataFlow::ConfigSig { /** * 定义【污点源】(Source) * 即:攻击者可以控制的输入数据从哪里进入系统。 */ predicate isSource(DataFlow::Node source) { // RemoteFlowSource 涵盖了常见的 Web 框架入口 // 例如 Spring Boot 的 @RequestParam String target source instanceof RemoteFlowSource }
/** * 定义【汇聚点】(Sink) * 即:危险的操作在哪里执行,且使用了被污染的数据。 */ predicate isSink(DataFlow::Node sink) { exists(MethodCall ma | // 匹配方法名为 "exec" 的调用 ma.getMethod().hasName("exec") and // 且该方法属于 java.lang.Runtime 类 ma.getMethod().getDeclaringType().hasQualifiedName("java.lang", "Runtime") and // 污点数据(sink)是该方法的第一个参数 (index 0) sink.asExpr() = ma.getArgument(0) ) }
/** * 定义【屏障/净化器】(Barrier/Sanitizer) * 最新版 API 中使用 isBarrier。如果数据经过此逻辑,则认为它变安全了,停止追踪。 */ predicate isBarrier(DataFlow::Node node) { exists(MethodCall ma | // 如果数据作为参数传入了 isValidIp 方法 ma.getMethod().hasName("isValidIp") and // 这里的 node 代表该方法的参数,在此处截断污点流 node.asExpr() = ma.getArgument(0) ) } }
/** * 【实例化】 * 将我们定义的配置(ConfigSig)注入到 TaintTracking 引擎中。 * Global 模块会自动处理所有复杂的图计算逻辑。 */ module CommandInjectionFlow = TaintTracking::Global<CommandInjectionConfig>;
/** * 【导入路径图】 * 必须从生成的流量模块中导入 PathGraph,才能在 select 中使用 PathNode 类型。 */ import CommandInjectionFlow::PathGraph
/** * 【最终查询】 * 使用 from-where-select 结构输出结果。 */ from CommandInjectionFlow::PathNode source, CommandInjectionFlow::PathNode sink where // 调用引擎计算是否存在从 source 到 sink 的可达路径 CommandInjectionFlow::flowPath(source, sink) select sink.getNode(), // 漏洞触发点 source, // 攻击路径起点(用于绘图) sink, // 攻击路径终点(用于绘图) "Potential command injection from $@.", // 提示信息,$@ 占位符会被后续参数填充 source.getNode(), // 对应 $@ 的第一个链接点 "user input" // 对应 $@ 的链接文字
|

要将库目录加入工作区并且
设置qlpack.yml,codeql会在当前工作区自动寻找库的位置。
1 2 3 4
| name: my-custom-queries version: 1.0.0 dependencies: codeql/java-all: "*"
|
右键运行

运行后结果
