CodeQL 文档

为 Java 和 Kotlin 定制库模型

您可以对控制任何框架或库中数据流的方法和可调用项进行建模。这对于自定义框架或不受标准 CodeQL 库支持的小众库特别有用。

注意

针对 Kotlin 的 CodeQL 分析目前处于测试阶段。在测试期间,对 Kotlin 代码的分析及其随附文档不会像其他语言那样全面。

测试版通知 - 不稳定的 API

使用数据扩展进行库定制目前处于测试阶段,可能会发生变化。

在测试阶段,可能会对此格式进行重大更改。

关于本文

本文包含有关如何在数据扩展文件中为 Java 依赖项定义自定义模型(摘要、接收器和源)的参考材料。

创建自己的模型的最佳方法是使用适用于 Visual Studio Code 的 CodeQL 扩展中的 CodeQL 模型编辑器。模型编辑器会自动引导您完成定义模型的过程,显示您需要定义的属性和可用的选项。您可以将生成的模型作为数据扩展文件保存在 CodeQL 模型包中,并在不担心语法的情况下使用它们。

有关更多信息,请参阅 GitHub 文档中的使用 CodeQL 模型编辑器

关于数据扩展

您可以通过在数据扩展文件中定义代码依赖项的模型(摘要、接收器和源)来自定义分析。每个模型都定义了库或框架的一个或多个元素的行为,例如方法和可调用项。运行数据流分析时,这些模型会扩展数据流分析跟踪的潜在源和接收器,并提高结果的精度。

大多数安全查询都会搜索从不受信任的输入源到代表漏洞的接收器的路径。这称为污点跟踪。每个源都是数据流分析跟踪受污染数据的起点,每个接收器都是终点。

污点跟踪查询还需要知道数据如何流经未包含在源代码中的元素。这些元素被建模为摘要。摘要模型使查询能够综合通过存储库中未存储的依赖项代码中的元素的流行为。

用于在扩展文件中定义元素的语法

每个元素模型都使用数据扩展定义,其中每个元组构成一个模型。用于扩展 CodeQL 附带的标准 Java 查询的数据扩展文件是一个 YAML 文件,格式如下:

extensions:
  - addsTo:
      pack: codeql/java-all
      extensible: <name of extensible predicate>
    data:
      - <tuple1>
      - <tuple2>
      - ...

每个 YAML 文件可以包含一个或多个顶级扩展。

  • addsTo 定义 CodeQL 包名称和扩展注入到的可扩展谓词。
  • data 定义作为值注入可扩展谓词的一行或多行元组。列数及其类型必须与可扩展谓词的定义相匹配。

数据扩展使用联合语义,这意味着将单个可扩展谓词的所有扩展的元组组合在一起,删除重复项,并且可以通过引用可扩展谓词来查询所有剩余的元组。

将数据扩展文件发布在 CodeQL 模型包中以进行共享

您可以将一个或多个数据扩展文件分组到 CodeQL 模型包中,并将其发布到 GitHub Container Registry。这使得任何人都可以轻松下载模型包并使用它来扩展他们的分析。有关更多信息,请参阅 CodeQL CLI 文档中的创建 CodeQL 模型包发布和使用 CodeQL 包

用于在 Java 和 Kotlin 中创建自定义模型的可扩展谓词

适用于 Java 和 Kotlin 分析的 CodeQL 库公开了以下可扩展谓词:

  • sourceModel(package, type, subtypes, name, signature, ext, output, kind, provenance)。这用于对潜在受污染数据的来源进行建模。使用此谓词定义的源的kind 决定了它们与哪个威胁模型相关联。可以使用不同的威胁模型来自定义分析中使用的源。有关更多信息,请参阅“威胁模型”。
  • sinkModel(package, type, subtypes, name, signature, ext, input, kind, provenance)。这用于对可能以使代码易受攻击的方式使用受污染数据的接收器进行建模。
  • summaryModel(package, type, subtypes, name, signature, ext, input, output, kind, provenance)。这用于对流经元素的流量进行建模。
  • neutralModel(package, type, name, signature, kind, provenance)。这类似于摘要模型,但用于对对数据流分析影响较小的值的流进行建模。手动中性模型(其来源例如 manualai-manual)会覆盖生成的摘要模型(其来源例如 df-generated),以便忽略该摘要。除此之外,中性模型对数据流调度逻辑的影响很小,超出了本文档的讨论范围。

使用数据扩展文件中定义的模型填充可扩展谓词。

自定义模型定义示例

本节中的示例摘自 GitHub 发布的标准 CodeQL Java 查询包。它们演示了如何添加元组以扩展标准查询使用的可扩展谓词。

示例:java.sql 包中的污点接收器

此示例显示 Java 查询包如何将 execute 方法的参数建模为 SQL 注入接收器。这是位于 java.sql 包中的 Statement 类中的 execute 方法。

public static void taintsink(Connection conn, String query) throws SQLException {
    Statement stmt = conn.createStatement();
    stmt.execute(query); // The argument to this method is a SQL injection sink.
}

我们需要通过更新数据扩展文件来向 sinkModel(package, type, subtypes, name, signature, ext, input, kind, provenance) 可扩展谓词添加元组。

extensions:
  - addsTo:
      pack: codeql/java-all
      extensible: sinkModel
    data:
      - ["java.sql", "Statement", True, "execute", "(String)", "", "Argument[0]", "sql-injection", "manual"]

由于我们要添加新的接收器,因此我们需要向 sinkModel 可扩展谓词添加元组。前五个值标识要建模为接收器的可调用项(在本例中为方法)。

  • 第一个值 java.sql 是包名称。
  • 第二个值 Statement 是包含该方法的类(类型)的名称。
  • 第三个值 True 是一个标志,指示接收器是否也适用于该方法的所有重写。
  • 第四个值 execute 是方法名称。
  • 第五个值 (String) 是方法输入类型签名。

第六个值应留空,并且超出了本文档的讨论范围。其余值用于定义接收器的访问路径类型来源

  • 第七个值 Argument[0] 是传递给该方法的第一个参数的访问路径,这意味着这是接收器的位置。
  • 第八个值 sql-injection 是接收器的类型。接收器类型用于定义接收器在其中起作用的查询。在这种情况下 - SQL 注入查询。
  • 第九个值 manual 是接收器的来源,用于识别接收器的起源。

示例:来自 java.net 包的污点源

此示例展示了 Java 查询包如何将 getInputStream 方法的返回值建模为 remote 源。这是位于 java.net 包中的 Socket 类中的 getInputStream 方法。

public static void tainted(Socket socket) throws IOException {
    InputStream stream = socket.getInputStream(); // The return value of this method is a remote source of taint.
    ...
}

我们需要通过更新数据扩展文件,向 sourceModel(包、类型、子类型、名称、签名、扩展、输出、种类、来源) 可扩展谓词添加一个元组。

extensions:
  - addsTo:
      pack: codeql/java-all
      extensible: sourceModel
    data:
      - ["java.net", "Socket", False, "getInputStream", "()", "", "ReturnValue", "remote", "manual"]

由于我们要添加一个新源,因此我们需要向 sourceModel 可扩展谓词添加一个元组。前五个值标识要建模为源的可调用对象(在本例中为方法)。

  • 第一个值 java.net 是包名。
  • 第二个值 Socket 是包含源的类的名称(类型)。
  • 第三个值 False 是一个标志,指示源是否也适用于该方法的所有重写。
  • 第四个值 getInputStream 是方法名称。
  • 第五个值 () 是方法输入类型签名。

第六个值应留空,并且不在本文档的范围内。其余值用于定义源的 访问路径种类来源

  • 第七个值 ReturnValue 是方法返回的访问路径,这意味着返回值应被视为污点输入的来源。
  • 第八个值 remote 是源的种类。源种类用于定义源在范围内的威胁模型。remote 适用于许多与安全相关的查询,因为它表示来自不受信任数据的远程源。例如,SQL 注入查询使用 remote 源。有关更多信息,请参阅“威胁模型”。
  • 第九个值 manual 是源的来源,用于识别源的起源。

示例:添加通过 concat 方法的流

此示例展示了 Java 查询包如何为简单情况建模通过方法的流。此模式涵盖了许多我们需要总结通过存储在存储库外部的库或框架中的方法的流的情况。

public static void taintflow(String s1, String s2) {
    String t = s1.concat(s2); // There is taint flow from s1 and s2 to t.
    ...
}

我们需要通过更新数据扩展文件,向 summaryModel(包、类型、子类型、名称、签名、扩展、输入、输出、种类、来源) 可扩展谓词添加元组

extensions:
  - addsTo:
      pack: codeql/java-all
      extensible: summaryModel
    data:
      - ["java.lang", "String", False, "concat", "(String)", "", "Argument[this]", "ReturnValue", "taint", "manual"]
      - ["java.lang", "String", False, "concat", "(String)", "", "Argument[0]", "ReturnValue", "taint", "manual"]

由于我们要添加通过方法的流,因此我们需要向 summaryModel 可扩展谓词添加元组。每个元组定义从一个参数到返回值的流。第一行定义从限定符(示例中的 s1)到返回值(示例中的 t)的流,第二行定义从第一个参数(示例中的 s2)到返回值(示例中的 t)的流。

前五个值标识要建模为摘要的可调用对象(在本例中为方法)。以上两行的值相同,因为我们为同一个方法添加了两个摘要。

  • 第一个值 java.lang 是包名。
  • 第二个值 String 是类(类型)名称。
  • 第三个值 False 是一个标志,指示摘要是否也适用于该方法的所有重写。
  • 第四个值 concat 是方法名称。
  • 第五个值 (String) 是方法输入类型签名。

第六个值应留空,并且不在本文档的范围内。其余值用于定义摘要的 访问路径种类来源

  • 第七个值是输入的访问路径(数据从何处流入)。Argument[this] 是限定符的访问路径(示例中的 s1),Argument[0] 是第一个参数的访问路径(示例中的 s2)。
  • 第八个值 ReturnValue 是输出的访问路径(数据流向何处),在本例中为 ReturnValue,这意味着输入流向返回值。
  • 第九个值 taint 是流的种类。taint 表示污点通过调用传播。
  • 第十个值 manual 是摘要的来源,用于识别摘要的起源。

示例:添加通过 map 方法的流

此示例展示了 Java 查询包如何建模通过方法的更复杂流。在这里,我们对高阶方法和集合类型中的流进行建模。

public static void taintflow(Stream<String> s) {
  Stream<String> l = s.map(e -> e.concat("\n"));
  ...
}

我们需要通过更新数据扩展文件,向 summaryModel(包、类型、子类型、名称、签名、扩展、输入、输出、种类、来源) 可扩展谓词添加元组

extensions:
  - addsTo:
      pack: codeql/java-all
      extensible: summaryModel
    data:
      - ["java.util.stream", "Stream", True, "map", "(Function)", "", "Argument[this].Element", "Argument[0].Parameter[0]", "value", "manual"]
      - ["java.util.stream", "Stream", True, "map", "(Function)", "", "Argument[0].ReturnValue", "ReturnValue.Element", "value", "manual"]

由于我们要添加通过方法的流,因此我们需要向 summaryModel 可扩展谓词添加元组。每个元组定义构成通过 map 方法的总流的一部分流。前五个值标识要建模为摘要的可调用对象(在本例中为方法)。以上两行的值相同,因为我们为同一个方法添加了两个摘要。

  • 第一个值 java.util.stream 是包名。
  • 第二个值 Stream 是类(类型)名称。
  • 第三个值 True 是一个标志,指示摘要是否也适用于该方法的所有重写。
  • 第四个值 map 是方法名称。
  • 第五个值 Function 是方法输入类型签名。

第六个值应留空,并且不在本文档的范围内。其余值用于定义摘要定义的 访问路径种类来源

  • 第七个值是 输入 的访问路径(数据从何处流入)。
  • 第八个值是 输出 的访问路径(数据流向何处)。

对于第一行

  • 第七个值是 Argument[this].Element,这是限定符元素的访问路径(示例中流 s 的元素)。
  • 第八个值是 Argument[0].Parameter[0],这是 mapFunction 参数的第一个参数的访问路径(示例中的 lambda 参数 e)。

对于第二行

  • 第七个值是 Argument[0].ReturnValue,这是 mapFunction 参数的返回值的访问路径(示例中 lambda 的返回值)。
  • 第八个值是 ReturnValue.Element,这是 map 返回值的元素的访问路径(示例中流 l 的元素)。

对于两行的其余值

  • 第九个值 value 是流的种类。value 表示值被保留。
  • 第十个值 manual 是摘要的来源,用于识别摘要的起源。

也就是说,第一行指定值可以从限定符流的元素流入提供给 map 的函数的第一个参数。第二行指定值可以从函数的返回值流入从 map 返回的流的元素。

示例:添加 neutral 方法

此示例展示了 Java 查询包如何将 now 方法建模为相对于流而言是中性的。中性模型用于定义没有流经方法。

public static void taintflow() {
    Instant t = Instant.now(); // There is no flow from now to t.
    ...
}

我们需要通过更新数据扩展文件,向 neutralModel(包、类型、名称、签名、种类、来源) 可扩展谓词添加一个元组。

extensions:
- addsTo:
    pack: codeql/java-all
    extensible: neutralModel
  data:
    - ["java.time", "Instant", "now", "()", "summary", "manual"]

由于我们要添加一个中性模型,因此我们需要向 neutralModel 可扩展谓词添加元组。前四个值标识要建模为中性的可调用对象(在本例中为方法),第五个值是种类,第六个值是中性的来源。

  • 第一个值 java.time 是包名。
  • 第二个值 Instant 是类(类型)名称。
  • 第三个值 now 是方法名称。
  • 第四个值 () 是方法输入类型签名。
  • 第五个值 summary 是中性点的类型。
  • 第六个值 manual 是中性点的来源。

威胁模型

注意

威胁模型目前处于测试阶段,可能会发生变化。在测试阶段,只有 Java 和 C# 分析支持威胁模型。

威胁模型是一类命名的可独立启用或禁用的数据流源。威胁模型允许您控制要视为不安全的数据流源集。例如,一个代码库可能只将远程 HTTP 请求视为受污染的,而另一个代码库也可能将来自本地文件的数据视为不安全的。您可以使用威胁模型来确保在 CodeQL 分析中使用相关污染源。

sourceModelkind 属性决定了源与哪个威胁模型相关联。主要有两类:

  • remote 表示来自网络的请求和响应。
  • local 表示来自本地文件的数据(file)、命令行参数(commandargs)、数据库读取(database)和环境变量(environment)。

运行 CodeQL 分析时,默认包含 remote 威胁模型。在使用 CodeQL CLI 和 GitHub 代码扫描时,您可以选择性地包含其他威胁模型。有关更多信息,请参阅使用 CodeQL 查询分析代码自定义代码扫描的高级设置

  • ©GitHub 公司
  • 条款
  • 隐私