教程
官方:https://codeql.githubdocs.cn/docs/codeql-overview/
CodeQL 入门
环境配置
- codeql-cli:https://github.com/github/codeql-cli-binaries/releases
- codeql-sdk:https://github.com/github/codeql.git
可分开安装在 /usr/local 目录下,并写入环境变量。
貌似要很好的适配只能使用vscode,cursor之类的貌似不太适配:
由于 CodeQL 是 GitHub 开源的,而 GitHub 又被微软全资收购了,且 Visual Studio Code 又是微软开源的,所以 CodeQL 只对 Visual Studio Code 做了全面友好的支持,我们如果需要本地使用CodeQL,那就不得不下载 Visual Studio Code!
CodeQL 分析包括三个步骤
- 通过创建 CodeQL 数据库来准备代码
- 针对数据库运行 CodeQL 查询
- 解释查询结果
因此第一步需要创建数据库来进行查询,它把源码转换成“可查询的结构化数据”。由于c++是需要编译的语言,正常情况codeql需要先对c++进行编译,然后构建对应的 database,这样后续的分析就会包括编译生成的 Generated code。但主包手里的 C++ 仓库编译不了,卡在这一步很久,查阅文档发现目前 C++ 已经支持不用 build就可以查询。https://github.blog/changelog/2025-10-14-codeql-scanning-rust-and-c-c-without-builds-is-now-generally-available/
只需要设置 --build-mode none 就可以直接开始构建了。
一些 CodeQL CLI 的命令:
1 | > codeql database create codeql-dbs --source-root=src \\ |
不过要想做全面扫描,肯定还是带编译的建库更好,所以编译这一块还是要想一下怎么搞。
QL 查询
常见的查询方式类似于
1 | from /* ... variable declarations ... */ |
变量的类型常见的有:
- 字符串 - string
- 浮点数 - float
- 布尔值 - boolean
- 日期(日/月/年) - date
Example
1 | import tutorial |
C / C++ 学习
基础查询
函数查找
使用 Function 和 FunctionCall 来查找对函数 sprintf 的调用。
1 | import cpp |
表达式查找
查找将 0 赋值给整数
1 | import cpp |
数据流
使用数据流分析来跟踪可能存在恶意或不安全数据流,这些数据流会导致代码库中的漏洞。
本地数据流 与 Node
本地数据流(local dataflow):只考虑同一个函数(或同一个作用域)内部的数据如何流动。比如:函数体里 a = b; c = a;,都是本地数据流。本地数据流库位于模块 DataFlow 中,该模块定义了表示数据可以流经的任何元素的类 Node(表示“数据流图上一个节点”的抽象。一个节点可以表示“某个表达式的值”或“某个参数槽”。)。 Node 分为表达式节点 (ExprNode, IndirectExprNode) 和参数节点 (ParameterNode, IndirectParameterNode)。间接节点表示经过固定数量的指针解引用后的表达式或参数。Node 类提供一些成员谓词把它映射回源码层面的 Expr 或 Parameter。
1 | class Node { |
本地污点跟踪
本地污点追踪(local taint)在 local dataflow 基础上做两方面扩展:
- 包含非“值传递”的传播:数据“污点”不总是以“值从一个变量拷贝到另一个变量”的形式出现。某些函数使用值但不复制它们的值(non-value-preserving):例如
malloc(i * sizeof(...)),这里i并没有“值传递”给malloc的返回值,但i的值影响了堆大小 —— 这是语义上“污点到用来分配大小的参数”的传播(对安全问题很重要,称作非值保留传播)。 - 扩展节点种类:localTaintStep 可能定义额外的“传播原语”,例如“函数参数作为长度参数会被认为是传播到内存分配行为”,或“某些API会把参数当作配置/长度/size/flags”等等。
可以按以下方式查找从参数 source 到表达式 sink 在零个或多个本地步骤中的污点传播,其中 nodeFrom 和 nodeTo 的类型为 DataFlow::Node
1 | nodeFrom.asParameter() = source |
e.g. 命令注入
以通过 system 调用存在变量的查询为例:
1 | import cpp |
污点追踪
要进一步判断变量是否外部可控,需要使用 codeql 的污点跟踪功能,由 TaintTracking 模块提供。codeql 支持 local 和 global 两种模式,期中 local 的污点追踪只能追踪函数内的代码,而 global 则会在整个源码中进行追踪。以 local 模式为例,TaintTracking::localTaint(source, sink) 含义就是查找从 source 到 sink 的查询。如:用户可控输入的参数 userinput 为 source,系统调用执行变量的 system 函数是 sink 点。污点追踪可以查询 system(sink) 的变量由 userinput(source) 返回中控制的调用点。
但如果这一系列过程不是在一个函数内完成的,比如 system 调用(sink)或 get_user_input (source)的操作封装成了函数,这样使用 local 模式就会漏(无法追踪函数外的部分,且很容易漏掉新的 wrapper ),除非能把封装的函数也考虑在内,因此可以选用 global 模式进行查询,或者把对应封装的函数也包含进去。
污点传播的过程大概可以分为三个阶段:
- 定位
source点,source点代表来源,一般是用户可控的输入作为污点。- 定位
sink点,sink点一般是敏感函数,比如sql查询等。- 污点传播/回溯,当判断
source点能传播到sink的时候,报告漏洞和对应传播路径。如果存在一些安全过滤,那么需要再加入sanitizer/clean点,来去除污点。——by woodpeckerjs
简易代码实现:
1 | import cpp |
继承 TaintTracking::Configuration 用来定义“一个污点分析的配置”,这个配置会告诉数据流引擎哪些节点是source,哪些是sink,以及其他选项。在下面覆写 isSource 和 isSink ,最后通过hashFlowPath 直接查询从 source 到 sink 的数据流 path。(它能处理跨函数调用、通过参数/返回、通过字段/成员、通过堆分配/加载/存储等传播,只要配置允许,CodeQL 的 C++ 数据流 extractor 已经支持大多数传播原语)
但是上述查询还有很多不完善的地方,如source和sink包含的范围;数据流过程中经过一些escape操作的情况需要过滤(sanitizers);某些函数会把污点从参数传回返回值或从参数到参数(propagation)
较详细版本实现:
1 | import cpp |
参考
- https://github.blog/changelog/2025-10-14-codeql-scanning-rust-and-c-c-without-builds-is-now-generally-available/
- https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
- https://www.cnblogs.com/you-fish/p/18349266
- https://xz.aliyun.com/news/8774