CodeQL从入门到入土
字数 1710 2025-08-18 11:35:44

CodeQL 从入门到精通:静态代码分析与漏洞检测实战指南

1. CodeQL 概述

1.1 什么是 CodeQL

CodeQL 是由 GitHub 开发的一种革命性的语义代码查询语言,它是一种强大的静态代码分析工具,基于高级数据流分析技术,能够深入理解代码结构、语义和行为。

核心特点

  • 语义代码查询语言,能理解代码上下文
  • 内置强大的数据流判断能力
  • 可自定义补充各种例外场景
  • 支持多种编程语言(C、C++、Java、Python等)
  • 提供丰富的内置库和查询示例

1.2 CodeQL 工作原理

CodeQL 分析流程基于污点分析的三元组概念:

  1. Source(源):漏洞污染链条的输入点(如外部可控变量、入参)
  2. Sink(汇):漏洞污染链条的执行点(如执行SQL语句的函数)
  3. Sanitizer(净化):阻断传播链的方法

2. CodeQL 安装与配置

2.1 安装组件

CodeQL 包含两部分:

  1. 解析引擎+SDK(开源):包含官方规则库和各种内置QL脚本
    • 地址:codeql-sdk
  2. 数据库编译引擎(闭源):将源代码转换为抽象语法树
    • 地址:codeql-cli

2.2 环境配置

  1. 将 CodeQL 路径添加到 PATH 环境变量
  2. 验证安装:命令行运行 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. 最佳实践与技巧

  1. 编写思路:由内到外,先获取小范围数据再判断是否属于大目标
  2. 版本管理:生成database的codeql.exe版本和规则代码版本需保持一致
  3. 调试技巧
    • 使用select语句逐步验证中间结果
    • 关注数据流路径是否完整
  4. 性能优化
    • 合理限制查询范围
    • 使用高效的谓词和条件

8. 总结

CodeQL 是一个强大的静态代码分析工具,特别适合:

  • 发现内存泄漏、空指针引用、SQL注入等常见漏洞
  • 自定义代码质量检查规则
  • 长期监控系统代码健康状况

掌握 CodeQL 需要:

  1. 深入理解目标语言的语法和语义
  2. 熟悉常见漏洞模式和数据流分析
  3. 熟练使用 CodeQL 的查询语法和内置库

通过本文的学习,您应该已经掌握了 CodeQL 的核心概念和基本用法,能够开始编写自己的代码分析查询了。

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为例) 注意事项 : 源码目录不能有中文 注意网络问题(如公司内部网络可能需要添加证书) 3. CodeQL 基本语法 3.1 基本查询结构 3.2 谓词(Predicates) 无返回值谓词 用于限定数据集范围 有返回值谓词 类似函数,使用 result 关键字返回结果 3.3 类的定义 CodeQL 中的类不是建立对象,而是建立数据集 3.4 迭代 用于嵌套场景,使用 + 和 * 表示迭代次数 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 污点分析配置 5.2 常用句式示例 Java代码与XML文件关联 获取指定注解 显示源码位置 6. 实战案例:批量操作参数检测 7. 最佳实践与技巧 编写思路 :由内到外,先获取小范围数据再判断是否属于大目标 版本管理 :生成database的codeql.exe版本和规则代码版本需保持一致 调试技巧 : 使用select语句逐步验证中间结果 关注数据流路径是否完整 性能优化 : 合理限制查询范围 使用高效的谓词和条件 8. 总结 CodeQL 是一个强大的静态代码分析工具,特别适合: 发现内存泄漏、空指针引用、SQL注入等常见漏洞 自定义代码质量检查规则 长期监控系统代码健康状况 掌握 CodeQL 需要: 深入理解目标语言的语法和语义 熟悉常见漏洞模式和数据流分析 熟练使用 CodeQL 的查询语法和内置库 通过本文的学习,您应该已经掌握了 CodeQL 的核心概念和基本用法,能够开始编写自己的代码分析查询了。