CodeQL从入门到入土
字数 1710 2025-08-18 11:35:44
CodeQL 从入门到精通:静态代码分析与漏洞检测实战指南
1. CodeQL 概述
1.1 什么是 CodeQL
CodeQL 是由 GitHub 开发的一种革命性的语义代码查询语言,它是一种强大的静态代码分析工具,基于高级数据流分析技术,能够深入理解代码结构、语义和行为。
核心特点:
- 语义代码查询语言,能理解代码上下文
- 内置强大的数据流判断能力
- 可自定义补充各种例外场景
- 支持多种编程语言(C、C++、Java、Python等)
- 提供丰富的内置库和查询示例
1.2 CodeQL 工作原理
CodeQL 分析流程基于污点分析的三元组概念:
- Source(源):漏洞污染链条的输入点(如外部可控变量、入参)
- Sink(汇):漏洞污染链条的执行点(如执行SQL语句的函数)
- Sanitizer(净化):阻断传播链的方法
2. CodeQL 安装与配置
2.1 安装组件
CodeQL 包含两部分:
- 解析引擎+SDK(开源):包含官方规则库和各种内置QL脚本
- 地址:
codeql-sdk
- 地址:
- 数据库编译引擎(闭源):将源代码转换为抽象语法树
- 地址:
codeql-cli
- 地址:
2.2 环境配置
- 将 CodeQL 路径添加到 PATH 环境变量
- 验证安装:命令行运行
codeql -v应有输出
2.3 创建数据库(以Java为例)
codeql database create java-database --language=java \
--command="mvn clean install -Dmaven.test.skip=true --settings 路径/setting.xml"
注意事项:
- 源码目录不能有中文
- 注意网络问题(如公司内部网络可能需要添加证书)
3. CodeQL 基本语法
3.1 基本查询结构
import <language> /* 导入对应的语言包 */
from [datatype] var /* 构建数据类型表 */
where condition[var = something] /* 设置逻辑表达式 */
select var /* 打印结果 */
3.2 谓词(Predicates)
无返回值谓词
用于限定数据集范围
predicate isList(Parameter p) {
p.getType().toString().indexOf("List<") != -1
}
有返回值谓词
类似函数,使用 result 关键字返回结果
int test(int i) {
result = i + 1 and i in [1..19]
}
select test(3) // 输出4
3.3 类的定义
CodeQL 中的类不是建立对象,而是建立数据集
class MyBatisMapperXmlFile extends XmlFile {
MyBatisMapperXmlFile() {
count(XmlElement e|e=this.getAChild())=1 and
this.getAChild().getName()="mapper"
}
}
3.4 迭代
用于嵌套场景,使用 + 和 * 表示迭代次数
Person ancestorOf(Person p) {
result = parentOf(p) or result = parentOf(ancestorOf(p))
}
4. 常用语法总结
4.1 核心类与方法
| 类/方法 | 描述 |
|---|---|
Method |
方法类,获取所有方法声明 |
MethodAccess/MethodCall |
方法调用类,获取方法实际调用 |
Parameter |
参数类,获取所有参数 |
Annotation |
注解类,获取所有注解 |
Expr |
表达式类,所有有值、有类型的统称 |
XmlFile |
XML文件类 |
4.2 MethodCall 常用方法
| 方法 | 描述 |
|---|---|
getAnArgument |
获取方法调用的所有参数 |
getArgument |
获取指定参数 |
getEnclosingCallable |
获取包含此调用方法的类或方法 |
getMethod |
获取方法的实现 |
getQualifier |
获取方法调用的主体 |
注意:CodeQL 版本更新可能导致类名变更(如 MethodAccess 改为 MethodCall)
5. 污点追踪实战
5.1 污点分析配置
class TestVul extends TaintTracking::Configuration {
TestVul() { this = "Test" }
predicate isTaintedString(Expr expSrc, Expr expDest) {
// 定义两个表达式的关系
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
isTaintedString(node1.asExpr(), node2.asExpr())
}
override predicate isSource(DataFlow::Node Source) {
// 定义source
}
override predicate isSink(DataFlow::Node sink) {
// 定义sink
}
}
5.2 常用句式示例
Java代码与XML文件关联
class MyBatisMapperSqlOperationWithProgram extends MyBatisMapperXmlElement {
Method getMethod() {
result.getName() = this.getAttribute("id").getValue() and
result.getDeclaringType() = this.getParent().(MyBatisMapperXmlElement).getNamespaceRefType()
}
}
获取指定注解
class PostAnn extends Annotation {
PostAnn() {
this.getType().hasQualifiedName("org.springbootframework.web.bind.annotation","PostMapping")
}
}
显示源码位置
from Call c, Method m
where m = c.getCallee() and m.hasName("insert")
select c, c.getLocation.getFile().getRelativePath()+":"+c.getLocation().getStartLine(), c.getCaller()
6. 实战案例:批量操作参数检测
/**
* @id java/examples/vuldemo
* @name Dos
* @description Dos
* @kind path-problem
* @problem.severity recommendation
* @tags security
*/
import java
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph
// 去除set方法
class NoSetClass extends Parameter {
NoSetClass() {
not(this.getCallable().getReturnType().toString() = "void" and
this.getCallable().toString().matches("set%") and
this.getCallable().getNumberOfParameters() = 1)
}
}
// 获取参数,要求没有注解或是有注解但注解不是Size
class NoAnnOrnotAn extends Parameter {
NoAnnOrnotAn() {
exists(Annotation an |
not an.getType().hasName("Size") and
this=an.getTarget()
) or not exists(Annotation an|this=an.getTarget())
}
}
// 实际获取的入参,用上面的条件限制
class ListParameter extends Parameter {
ListParameter() {
this.getType() instanceof CollectionType and
this instanceof NoAnnOrnotAn and
this instanceof NoSetClass
}
}
class Batch_Dos extends TaintTracking::Configuration {
Batch_Dos() { this = "FuckDos" }
// source类型声明
override predicate isSource(DataFlow::Node source) {
source.asParameter() instanceof ListParameter
}
// sink定义为dao操作,涉及集合类型
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess m,Expr e |
sink.asExpr()=e and
m.getAnArgument().getType() instanceof CollectionType and
m.getAChildExpr().toString().indexOf("Dao") = m.getAChildExpr().toString().length() - 3 and
e = m.getAnArgument()
)
}
}
from Batch_Dos batch, DataFlow::PathNode source, DataFlow::PathNode sink
where batch.hasFlowPath(source,sink)
select source.getNode(),source,sink,"source"
7. 最佳实践与技巧
- 编写思路:由内到外,先获取小范围数据再判断是否属于大目标
- 版本管理:生成database的codeql.exe版本和规则代码版本需保持一致
- 调试技巧:
- 使用select语句逐步验证中间结果
- 关注数据流路径是否完整
- 性能优化:
- 合理限制查询范围
- 使用高效的谓词和条件
8. 总结
CodeQL 是一个强大的静态代码分析工具,特别适合:
- 发现内存泄漏、空指针引用、SQL注入等常见漏洞
- 自定义代码质量检查规则
- 长期监控系统代码健康状况
掌握 CodeQL 需要:
- 深入理解目标语言的语法和语义
- 熟悉常见漏洞模式和数据流分析
- 熟练使用 CodeQL 的查询语法和内置库
通过本文的学习,您应该已经掌握了 CodeQL 的核心概念和基本用法,能够开始编写自己的代码分析查询了。