在数据库中运行 CodeQL

已完成

将代码提取到数据库后,现在可以使用 CodeQL 查询对其进行分析。 GitHub 专家、安全研究人员和社区参与者编写和维护默认 CodeQL 查询。 还可以编写自己的查询。

可以在代码扫描分析中使用 CodeQL 查询来查找源代码中的问题,并查找潜在的安全漏洞。 还可以编写自定义查询来识别源代码中使用的每种语言的问题。

有两种重要的查询类型:

  • 警报查询 在代码的特定位置显示问题。
  • 路径查询 描述代码中的源和接收器之间的信息流。

简单 CodeQL 查询

基本 CodeQL 查询结构具有文件扩展名 .ql 并包含子 select 句。 下面是一个示例查询结构:

/**
 *
 * Query metadata
 *
 */
import /* ... CodeQL libraries or modules ... */
/* ... Optional, define CodeQL classes and predicates ... */
from /* ... variable declarations ... /
where / ... logical formula ... /
select / ... expressions ... */

查询元数据

将 CodeQL 用于代码扫描时会转换结果,以突出显示查询所设计要查找的潜在问题。 查询包含指示应如何解释结果的元数据属性。 使用查询元数据可以:

  • 将自定义查询添加到 GitHub 存储库时标识这些查询。
  • 提供有关查询用途的信息。

元数据信息可以包括查询的说明、唯一 ID 及其问题类型(警报或路径)。 元数据还指定如何解释和显示查询结果。

GitHub 具有查询元数据的建议样式指南。 可以在 CodeQL 文档中找到它。

此示例显示了其中一个标准 Java 查询的元数据:

显示查询元数据的屏幕截图。

CodeQL 不解释没有元数据的查询。 它将这些结果显示为表,并且不会在源代码中显示它们。

QL 语法

QL 是一种声明性、面向对象的查询语言。 它经过优化,可以高效分析分层数据结构,特别是表示软件项目的数据库。

QL 的语法类似于 SQL,但 QL 的语义基于 Datalog。 Datalog 是一种声明性逻辑编程语言,通常用作查询语言。 由于 QL 主要是逻辑语言,因此 QL 中的所有作都是逻辑作。 QL 还从 Datalog 中继承了递归谓词。 QL 添加了对聚合的支持,使复杂的查询简洁简单。

QL 语言由逻辑公式组成。 它使用常见的逻辑连接,例如 andornot,以及限定符,如 forallexists。 由于 QL 继承递归谓词,因此还可以使用基本的 QL 语法和聚合(例如 countsumaverage编写复杂的递归查询。

有关 QL 语言的详细信息,请参阅 CodeQL 文档

路径查询

信息流经程序的方式很重要。 看似良性的数据可能以意外的方式流动,从而允许其恶意使用。

创建路径查询有助于通过代码库可视化信息流。 查询可以跟踪数据从其可能起点(source)到其可能的终结点()获取的路径。sink 若要为路径建模,查询必须提供有关链接路径的源、接收器和数据流步骤的信息。

开始编写自己的路径查询的最简单方法是使用现有查询之一作为模板。 若要获取这些受支持语言的查询,请参阅 CodeQL 文档

路径查询需要某些元数据、查询谓词和 select 语句结构。 CodeQL 中的许多内置路径查询都遵循基本结构。 结构取决于 CodeQL 如何对要分析的语言进行建模。

下面是路径查询的示例模板:

/**
 * ...
 * @kind path-problem
 * ...
 */

import <language>
// For some languages (Java/C++/Python/Swift), you need to explicitly import the data-flow library, such as
// import semmle.code.java.dataflow.DataFlow or import codeql.swift.dataflow.DataFlow
...

module Flow = DataFlow::Global<MyConfiguration>;
import Flow::PathGraph

from Flow::PathNode source, Flow::PathNode sink
where Flow::flowPath(source, sink)
select sink.getNode(), source, sink, "<message>"

在该模板中:

  • MyConfiguration是一个模块,其中包含定义数据如何在sourcesink之间流动的谓词。
  • Flow 是基于 MyConfiguration 的数据流计算结果。
  • Flow::Pathgraph 是需要导入的数据流图形模块,以便在查询中包含路径说明。
  • source 并且 sink 是配置中定义的图形中的节点,并且 Flow::PathNode 是它们的类型。
  • DataFlow::Global<..> 是数据流的调用。 可以改用 TaintTracking::Global<..> 来包含一组默认的污点步骤。

如何编写路径查询

查询需要计算路径图才能生成路径说明。 为此,请定义名为edges的查询谓词。 查询谓词是具有query 批注的非成员谓词。 查询注释返回谓词计算的所有元组。

edges谓词定义要计算的图形的边缘关系。 它用于计算与查询生成的每个结果相关的路径。 还可以从其中一个标准数据流库中的路径图模块导入预定义 edges 的谓词。

数据流库包含数据流分析中常用的其他类、谓词和模块,以及路径图模块。 CodeQL 数据流库通过对数据流图建模或实现数据流分析来运行。 普通数据流库用于分析信息流,其中数据值在每个步骤中得以保留。

下面是一个示例语句,该语句从数据流库(pathgraph)导入DataFlow.qll模块,其中edges定义了:

import DataFlow::PathGraph

可以导入 CodeQL 附带的许多其他库。 还可以导入专为在各种常见框架和环境中实现数据流分析而设计的库。

该类 PathNode 旨在实现数据流分析。 它是一个增加了调用上下文(接收器除外)、访问路径和配置的 Node。 仅生成可从源访问的 PathNode 值。

下面是导入路径的示例:

import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl

可以选择定义查询 nodes 谓词,该谓词指定所有语言的路径图的节点。 定义 nodes时,所选节点仅定义具有终结点的边缘。 如果未定义 nodes,则需要选择所有可能的终结点 edges

数据库分析

使用查询分析 CodeQL 数据库时,会在源代码的上下文中收到有意义的结果。 结果采用 SARIF 或其他解释格式的警报或路径样式。

下面是 CodeQL 数据库命令的示例,该命令通过针对数据库运行所选查询并解释结果来分析数据库:

codeql database analyze --format=<format> ---output=<output> [--threads=<num>] [--ram=<MB>] <options>... -- <database> <query|dir|suite>...

此命令结合了codeql database run-queriescodeql database interpret-results管道命令的效果。

或者,可以运行不符合解释为源代码警报要求的查询。 为此,请使用 codeql-database run-queriescodeql query run。 然后,用于 codeql bqrs decode 将原始结果转换为可读表示法。

可以在 CodeQL CLI 手册中获取可用 CodeQL CLI 命令的完整列表。

使用具有类别的 SARIF 文件

CodeQL 支持 SARIF 共享静态分析结果。 SARIF 旨在表示各种静态分析工具的输出。

使用 SARIF 输出进行 CodeQL 分析时,需要指定类别。 类别可以区分对同一提交存储库和不同语言或代码的不同部分执行的多个分析。 但是,具有相同类别的 SARIF 文件会相互覆盖。

当分析运行之间的类别值一致时,可以使用 CodeQL 扫描每个 SARIF 输出文件以分析同一代码库中的不同语言。 建议使用扫描的语言作为类别的标识符。

下面是一个示例。 类别值在 SARIF v1 中显示为 <run>.automationId 属性,在 SARIF v2 中显示为 <run>.automationLogicalId 属性,在 SARIF v2.1.0 中显示为 <run>.automationDetails.id 属性(如果尾部斜杠尚未存在,则附加一个)。

将 SARIF 结果发布到 GitHub

数据库准备就绪后,可以交互方式对其进行查询。 或者,可以运行一组查询,以 SARIF 格式生成一组结果,并将结果上传到 GitHub.com 的目标存储库:

codeql github upload-results --sarif=<file> [--github-auth-stdin] [--github-url=<url>] [--repository=<repository-name>] [--ref=<ref>] [--commit=<commit>] [--checkout-path=<path>] <options>...

若要将结果上传到 GitHub,请确保每个持续集成(CI)服务器都有一个 GitHub 应用或个人访问令牌,供 CodeQL CLI 使用。 必须使用具有写入权限的访问令牌或具有 security_events 写入权限的 GitHub 应用。

如果 CI 服务器已经使用具有此范围的令牌从 GitHub 签出存储库,则可能允许 CodeQL CLI 使用相同的令牌。 否则,请使用 security_events 写入权限创建新的令牌,并将此令牌添加到 CI 系统的机密存储中。 安全性的最佳做法是通过标准输入设置 --github-auth-stdin 标志并将令牌传递给命令。

上传 SARIF 结果

为了使代码扫描能够在您的 GitHub 存储库中显示来自非 Microsoft 静态分析工具的结果,您的结果必须存储在支持 SARIF 2.1.0 JSON 架构特定子集的 SARIF 文件中。 可以使用代码扫描 API 或 CodeQL CLI 上传结果。

每次上传新代码扫描的结果时,CodeQL 都会处理结果并将警报添加到存储库。 为防止出现相同问题的重复警报,代码扫描使用 SARIF partialFingerprints 属性匹配各种运行的结果,以便它们仅在所选分支的最新运行中出现一次。 消除重复项使得在编辑文件时,可以将警报与正确的代码行匹配。

在不同分析中,结果的规则 ID 必须保持相同。 指纹数据自动包含在通过 CodeQL 分析工作流或 CodeQL 运行程序创建的 SARIF 文件中。

SARIF 规范使用 JSON 属性名称 partialFingerprints,这是从命名指纹类型到指纹的字典。 该属性至少包含 primaryLocationLineHash 的值,该值根据主要位置的上下文提供指纹。

GitHub 会尝试在通过partialFingerprints操作上传 SARIF 文件时,从源文件填充缺少的upload-sarif字段。 此外,如果使用 API 终结点上传没有指纹数据的 /code-scanning/sarifs SARIF 文件,则处理和显示代码扫描警报时,用户可能会看到重复的警报。

若要避免在使用静态分析工具时看到重复的警报,请计算指纹数据并在上传 SARIF 文件之前填充 partialFingerprints 属性。 一个有用的起点是使用与 upload-sarif 操作相同的脚本。