传统的静态分析工具通常依赖于预定义的模式匹配或简单的抽象语法树扫描,在面对复杂的跨函数、跨文件数据流问题时显得力不从心,规则的扩展性也往往不尽如人意。
CodeQL的出现改变了这一局面。它最初由英国公司Semmle开发,在2019年被GitHub收购后,成为了GitHub Advanced Security的核心组件,并向开源社区免费开放。其核心创新在于将“代码视为数据”,通过构建代码数据库并提供强大的声明式查询语言(QL),使开发者能够像查询数据库一样,灵活、深度地挖掘代码中潜藏的安全漏洞与质量缺陷。
原理
核心思想:代码即数据
CodeQL将源代码及其编译过程中产生的所有信息——包括语法树、符号表、控制流、数据流等——转换成一个关系型数据库。这个数据库构建了代码的完整模型,涵盖:
- 抽象语法树(AST):代码的结构化表示。
- 数据流图:变量和值的传递路径。
- 控制流图(CFG):程序执行的路径。
- 调用图:函数之间的调用关系。
- 类型信息:变量、函数、类的类型层级。
一旦数据库构建完成,用户就可以使用CodeQL的查询语言QL,对这个数据库进行查询,从而精准定位代码中的特定模式或漏洞。
架构图

组件说明:
- 源代码:待分析的项目源码,支持多种语言(Go、Java、Python等)。
- CodeQL CLI / 数据库创建器:命令行工具,负责解析源代码并构建数据库。它会调用编译器或解释器来提取代码的抽象语法树、数据流图等信息。
- CodeQL 数据库:一个关系数据库,包含了代码的完整模型(语法、类型、数据流、控制流等),所有查询都将在该数据库上执行。
- QL 查询:用户编写的
.ql 文件,使用QL语言描述要查找的代码模式或漏洞。
- 标准库:CodeQL为每种语言预定义的库,提供了丰富的类、谓词和数据流分析模块,查询可以直接导入使用。
- QL 编译器:将用户查询与引用的标准库编译为可执行的逻辑计划。
- 查询引擎:在数据库上执行编译后的查询,利用数据库索引快速遍历代码关系,并返回结果集。
- 查询结果:命中的代码位置及相关数据,通常以表格形式展示,并可导出为SARIF等格式。
- 分析/报告:开发者或安全人员对结果进行解读、验证和修复。
交互图

流程说明:
- 创建数据库:
用户执行 codeql database create 命令,CLI调用数据库生成器,对源代码进行解析/编译,最终生成数据库文件。
- 运行查询:
用户执行 codeql query run 命令,CLI将数据库和查询文件交给查询引擎。
- 查询执行:
查询引擎首先编译QL查询(导入标准库),然后在数据库上执行数据流分析等操作,获取结果。
- 结果输出:
引擎返回结果集,CLI以可读格式(如表格、SARIF)输出,供用户进一步分析。
QL 语言
QL是一种声明式的逻辑编程语言,其语法和概念与SQL类似,但专门为代码分析设计。一个典型的QL查询由以下几部分组成:
import <语言库>
from <变量声明>
where <条件表达式>
select <输出表达式>
例如,要找出Java项目中所有名为 getUserInput 的方法:
import java
from Method m
where m.hasName(“getUserInput”)
select m
QL的强大之处在于其丰富的标准库。CodeQL为每种支持的语言都提供了一套预定义的谓词和类,例如 Method、Parameter、Expr、Call 等,并内置了数据流分析、污点跟踪等高级分析能力。
数据流分析与污点跟踪
数据流分析是CodeQL的核心能力之一,它能够追踪程序中值的流动路径,包括跨函数、跨文件的传递。
污点跟踪(Taint Tracking)则是数据流分析的一种特殊形式,它专注于追踪“不可信数据”(污点)如何从源(Source)传播到汇(Sink),并在传播过程中是否经过了有效的清理(Sanitizer)。CodeQL的标准库提供了强大的污点跟踪模块,用户可以轻松定义自己的源、汇和清理规则,从而高效发现各类注入漏洞(如SQL注入、XSS、命令注入等)。
使用场景
- 安全漏洞检测:自动发现代码中的常见高危漏洞,如跨站脚本(XSS)、SQL注入、路径遍历、反序列化漏洞等。
- 代码质量检查:识别潜在的错误模式,如空指针解引用、资源未关闭、死代码、条件永远为真/假等。
- 合规性审计:确保代码符合团队或行业的安全编码规范,例如检查敏感API的调用是否经过授权。
- 变种分析:当发现一个新漏洞时,可以使用CodeQL编写查询,快速在所有相关代码库中搜索该漏洞的变种,实现大规模安全回溯。
- 教育与研究:开发者可以通过编写QL查询深入理解代码结构和数据流;安全研究员也可利用它进行源码级别的漏洞挖掘。
使用 CodeQL 分析 Go 语言代码
本节将通过一个完整的示例,演示如何使用CodeQL分析一个Go项目,并编写自定义查询来检测潜在的路径遍历漏洞。
环境准备
- CodeQL CLI:从GitHub Releases下载并安装CodeQL命令行工具,并将
codeql 可执行文件添加到系统PATH。
- CodeQL 标准库:克隆 CodeQL仓库 到本地,其中包含了所有支持语言的库和预定义查询。
- VSCode 插件(可选):安装GitHub官方提供的CodeQL插件,可在IDE中直接编写、运行和调试查询。
为 Go 项目创建 CodeQL 数据库
假设我们有一个简单的Go Web应用,代码结构如下:
myapp/
├── main.go
├── handlers/
│ └── file.go
└── go.mod
在项目根目录执行以下命令,创建CodeQL数据库:
codeql database create ./codeql-db --language=go
CodeQL会自动检测项目使用的模块并编译,最终生成一个名为 codeql-db 的数据库文件夹。
编写第一个查询:查找所有 HTTP Handler
首先,创建一个QL包(一个包含 qlpack.yml 的文件夹)来存放我们的查询。在任意目录创建 my-go-queries 文件夹,并新建 qlpack.yml:
name: my-go-queries
version: 0.0.0
dependencies:
codeql/go-all: “*”
然后,在 my-go-queries 中创建 find-handlers.ql 文件,编写如下查询:
import go
// 查找所有被用作 HTTP 处理函数的函数
// 通常它们会被传递给 http.HandleFunc 或具有特定签名
from Function f
where
f.getACall() instanceof HttpHandleFuncCall // 存在调用点且调用目标为 http.HandleFunc
or
f.hasSignature(“func(http.ResponseWriter, *http.Request)”) // 或直接匹配函数签名
select f, f.getLocation()
说明:HttpHandleFuncCall 是CodeQL Go库中预定义的类,表示对 http.HandleFunc 的调用。运行此查询,结果将列出项目中所有符合HTTP handler特征的函数及其位置。
污点追踪详解
污点跟踪(Taint Tracking)是静态分析中的一项核心技术,用于追踪程序中不可信数据的传播路径,从而发现潜在的安全漏洞。在CodeQL中,污点追踪是内置的数据流分析能力之一,能够帮助安全工程师和开发者自动化地发现诸如SQL注入、跨站脚本(XSS)、命令注入、路径遍历等漏洞。
简单来说,污点追踪回答三个问题:
- 污点从哪里来? —— 识别源(Source):程序接收外部输入的入口点,例如HTTP请求参数、用户输入、文件读取、环境变量等。
- 污点流到哪里去? —— 识别汇(Sink):程序中执行敏感操作的位置,例如执行系统命令、拼接SQL查询、输出到HTML页面、文件操作等。
- 污点在传播过程中是否被清理? —— 识别清理器(Sanitizer):对污点数据进行验证、编码、转义或净化的操作,经过清理后数据不再被视为有害。
如果存在一条从源到汇的路径,且沿途没有经过有效的清理,那么就可能存在漏洞。
污点追踪在 CodeQL 中的实现
CodeQL为每种支持的语言都提供了强大的污点追踪库(例如 semmle.go.dataflow.TaintTracking),用户可以基于这些库快速构建自定义的污点分析查询。核心组件包括:
TaintTracking::Configuration:一个抽象类,用户通过继承它来定义自己的污点追踪配置。必须实现两个关键谓词:
isSource:定义哪些节点是污点源。
isSink:定义哪些节点是污点汇。
- (可选)
isSanitizer:定义哪些节点或操作会清除污点。
- (可选)
isAdditionalFlowStep:定义额外的污点传播步骤,用于处理库或框架的特殊数据流。
TaintTracking::PathProblem:一个模块,提供了将污点追踪结果转化为可读路径的内置功能,通常用于生成包含完整传播链的警报。
基本工作流程
- 导入污点追踪库:在查询文件开头导入相应语言的污点追踪模块。
- 定义源类:选择代表不可信输入的代码元素,可以是特定函数调用、参数、返回值等。
- 定义汇类:选择代表危险操作的代码元素,通常是敏感函数的特定参数。
- (可选)定义清理器:如果某些操作(如转义、验证)可以消除污点,将它们定义为清理器,以避免误报。
- 创建配置类:继承
TaintTracking::Configuration,将源、汇、清理器关联起来。
- 运行追踪:使用
hasFlowPath 谓词查找从源到汇的路径,并输出结果。
污点追踪概念交互图

说明:污点从源出发,在程序中通过各种操作传播。如果传播路径上遇到清理器,污点被清除,不再构成威胁;否则,一旦污点到达汇,即表明可能存在安全漏洞。
污点追踪工作流程(用户视角)

示例
CodeQL的污点追踪引擎能够自动处理这些常见的数据流步骤。下面通过一个Go语言示例,展示污点经过 TrimSpace、字符串拼接、ToLower 等多次修改后最终到达 db.Query 的完整链路。
package main
import (
“database/sql”
“net/http”
“strings”
)
// handler 是 HTTP 入口,接收用户输入
func handler(w http.ResponseWriter, r *http.Request) {
// 源:从 URL 查询参数获取 “id”
userInput := r.URL.Query().Get(“id”) // 污点起点
// 第一次修改:去除空格
trimmed := strings.TrimSpace(userInput)
// 第二次修改:拼接前缀
transformed := “user_” + trimmed
// 第三次修改:转为小写
final := strings.ToLower(transformed)
// 将最终结果传递给数据库查询函数
queryDB(final) // 污点最终流向汇
}
// queryDB 执行数据库查询
func queryDB(input string) {
db, _ := sql.Open(“sqlite3”, “:memory:”)
// 汇:直接将 input 拼接到 SQL 查询字符串中
db.Query(“SELECT * FROM users WHERE id = ‘” + input + “‘“)
}
污点传播链路

说明:每个箭头代表CodeQL能够自动识别的污点传播步骤(函数调用返回值依赖于参数、字符串拼接操作等)。从源 r.URL.Query().Get(“id”) 到汇 db.Query 的第一个参数,污点流经了7个节点。
CodeQL 查询:检测该链路
下面的查询将上述代码中的源和汇定义为污点追踪的起点和终点,并输出所有可达路径(包含中间步骤)。
/**
* @name 多步修改的污点追踪示例
* @description 检测从 HTTP 参数到 SQL 查询的污点流,包含多次函数调用修改。
* @kind path-problem
* @id go/examples/taint-multi-step
*/
import go
import semmle.go.dataflow.TaintTracking
import semmle.go.dataflow.TaintTracking::PathProblem
// 定义源:HTTP 请求的查询参数值
class HttpRequestParamSource extends DataFlow::Node {
HttpRequestParamSource() {
exists(DataFlow::CallNode call |
call = DataFlow::globalCall(“(*net/http.Request).URL.Query”) and
this = call.getResult().getACall(“Get”).getResult()
)
}
}
// 定义汇:database/sql 包中 Query 或 Exec 的第一个参数
class SqlQuerySink extends DataFlow::Node {
SqlQuerySink() {
exists(DataFlow::CallNode call |
call = DataFlow::globalCall(“database/sql.(*DB).Query”) and
this = call.getArgument(0)
)
or
exists(DataFlow::CallNode call |
call = DataFlow::globalCall(“database/sql.(*DB).Exec”) and
this = call.getArgument(0)
)
}
}
// 定义污点追踪配置
class MultiStepTaintConfig extends TaintTracking::Configuration {
MultiStepTaintConfig() { this = “MultiStepTaintConfig” }
override predicate isSource(DataFlow::Node source) {
source instanceof HttpRequestParamSource
}
override predicate isSink(DataFlow::Node sink) {
sink instanceof SqlQuerySink
}
}
// 执行追踪并输出路径
from MultiStepTaintConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, “用户输入从 $@ 流到 SQL 查询 $@,经过多次修改”,
source.getNode(), “用户输入”, sink.getNode(), “数据库查询”
查询结果解读
当此查询在示例代码上运行时,CodeQL会输出一条完整的污点传播路径。在VSCode插件中,可以展开路径查看每个步骤,类似:
1. source: r.URL.Query().Get(“id”) (handler.go:10)
2. step: userInput = r.URL.Query().Get(“id”) (handler.go:10)
3. step: trimmed = strings.TrimSpace(userInput) (handler.go:13)
4. step: transformed = “user_” + trimmed (handler.go:16)
5. step: final = strings.ToLower(transformed) (handler.go:19)
6. step: queryDB(final) (handler.go:22)
7. step: db.Query(“SELECT * FROM users WHERE id = ‘” + input + “‘”) (queryDB函数内)
8. sink: db.Query 的第一个参数 (queryDB.go:8)
每一步都对应代码中的具体位置,开发者可以清晰地看到污点如何从源头一步步流向危险函数。这种深度分析能力,正是CodeQL相较于传统工具的核心优势。
附录:资源链接
如果你希望深入研究更多复杂的技术文档与实战案例,欢迎来云栈社区交流探讨。