CodeQL 文档

为 C# 自定义库模型

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

Beta 通知 - 不稳定 API

使用数据扩展进行库自定义目前处于 Beta 版,可能会发生变化。

此格式在 Beta 版期间可能会发生重大更改。

关于本文

本文包含有关如何在数据扩展文件中为 C# 依赖项定义源、汇和流摘要的自定义模型的参考材料。

关于数据扩展

您可以通过在数据扩展文件中定义代码的 C#/.NET 依赖项的模型(摘要、汇和源)来自定义分析。每个模型定义库或框架中的一个或多个元素的行为,例如方法、属性和可调用项。当您运行数据流分析时,这些模型会扩展数据流分析跟踪的潜在源和汇,并提高结果的准确性。

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

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

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

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

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

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

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

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

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

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

用于在 C# 中创建自定义模型的可扩展谓词

CodeQL 库 for C# 分析公开以下可扩展谓词

  • sourceModel(namespace, type, subtypes, name, signature, ext, output, kind, provenance)。这用于对潜在污点数据的源进行建模。kind 使用此谓词定义的源决定它们与哪个威胁模型相关联。不同的威胁模型可用于自定义分析中使用的源。有关更多信息,请参阅“威胁模型。”
  • sinkModel(namespace, type, subtypes, name, signature, ext, input, kind, provenance)。这用于对汇进行建模,污点数据可能在其中以使代码易受攻击的方式使用。
  • summaryModel(namespace, type, subtypes, name, signature, ext, input, output, kind, provenance)。这用于对元素的流进行建模。
  • neutralModel(namespace, type, name, signature, kind, provenance)。这类似于摘要模型,但用于对对数据流分析影响很小的值的流进行建模。手动中性模型(具有诸如 manualai-manual 之类的来源)可用于覆盖生成的摘要模型(具有诸如 df-generated 之类的来源),这样摘要模型将被忽略。除此之外,中性模型没有其他影响。

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

自定义模型定义的示例

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

示例:System.Data.SqlClient 命名空间中的污点汇

此示例显示了 C# 查询包如何将 SqlCommand 构造函数的参数建模为 SQL 注入汇。这是 SqlCommand 类的构造函数,它位于 System.Data.SqlClient 命名空间中。

public static void TaintSink(SqlConnection conn, string query) {
     SqlCommand command = new SqlCommand(query, connection) // The argument to this method is a SQL injection sink.
     ...
}

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

extensions:
  - addsTo:
      pack: codeql/csharp-all
      extensible: sinkModel
    data:
      - ["System.Data.SqlClient", "SqlCommand", False, "SqlCommand", "(System.String,System.Data.SqlClient.SqlConnection)", "", "Argument[0]", "sql-injection", "manual"]

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

  • 第一个值 System.Data.SqlClient 是命名空间名称。
  • 第二个值 SqlCommand 是包含该方法的类的名称(类型)。
  • 第三个值 False 是一个标志,指示汇是否也适用于该方法的所有覆盖。
  • 第四个值 SqlCommand 是方法名称。构造函数以类命名。
  • 第五个值 (System.String,System.Data.SqlClient.SqlConnection) 是方法输入类型签名。类型名称必须是完全限定的。

第六个值应留空,并且不在本文档的范围内。其余值用于定义汇的 access pathkindprovenance(来源)。

  • 第七个值 Argument[0] 是传递给方法的第一个参数的 access path,这意味着这是汇的位置。
  • 第八个值 sql-injection 是汇的类型。汇类型用于定义汇在其中有效的查询。在本例中 - SQL 注入查询。
  • 第九个值 manual 是汇的来源,用于识别汇的来源。

示例:来自 System.Net.Sockets 命名空间的污点源

此示例显示了 C# 查询包如何将 GetStream 方法的返回值建模为 remote 源。这是 TcpClient 类中的 GetStream 方法,它位于 System.Net.Sockets 命名空间中。

public static void Tainted(TcpClient client) {
    NetworkStream stream = client.GetStream(); // The return value of this method is a remote source of taint.
    ...
}

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

extensions:
  - addsTo:
      pack: codeql/csharp-all
      extensible: sourceModel
    data:
      - ["System.Net.Sockets", "TcpClient", False, "GetStream", "()", "", "ReturnValue", "remote", "manual"]

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

  • 第一个值 System.Net.Sockets 是命名空间名称。
  • 第二个值 TcpClient 是包含该源的类的名称(类型)。
  • 第三个值 False 是一个标志,指示源是否也适用于该方法的所有覆盖。
  • 第四个值 GetStream 是方法名称。
  • 第五个值 () 是方法输入类型签名。

第六个值应留空,并且不在本文档的范围内。其余值用于定义源的 access pathkindprovenance(来源)。

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

示例:通过Concat方法添加流程

此示例展示了 C# 查询包如何为简单情况对方法的流程进行建模。此模式涵盖了许多需要汇总存储在库或框架(位于存储库之外)中的方法流程的情况。

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

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

extensions:
  - addsTo:
      pack: codeql/csharp-all
      extensible: summaryModel
    data:
      - ["System", "String", False, "Concat", "(System.Object,System.Object)", "", "Argument[0]", "ReturnValue", "taint", "manual"]
      - ["System", "String", False, "Concat", "(System.Object,System.Object)", "", "Argument[1]", "ReturnValue", "taint", "manual"]

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

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

  • 第一个值System是命名空间名称。
  • 第二个值String是类(类型)名称。
  • 第三个值False是一个标志,指示摘要是否也适用于该方法的所有覆盖。
  • 第四个值Concat是方法名称。
  • 第五个值(System.Object,System.Object)是方法输入类型签名。

第六个值应留空,并且不在本文档范围之内。其余值用于定义access pathkindprovenance(来源)摘要。

  • 第七个值是输入的访问路径(数据流出的位置)。Argument[0]是第一个参数(示例中的s1)的访问路径,Argument[1]是第二个参数(示例中的s2)的访问路径。
  • 第八个值ReturnValue是输出的访问路径(数据流入的位置),在本例中为ReturnValue,这意味着输入流入返回值。
  • 第九个值taint是流程的类型。 taint表示污染通过调用传播。
  • 第十个值manual是摘要的来源,用于标识摘要的来源。

还可以通过在第七个值中使用逗号分隔列表将两行合并为一行。如果方法有许多参数并且所有参数的流程都相同,这将很有用。

extensions:
  - addsTo:
      pack: codeql/csharp-all
      extensible: summaryModel
    data:
      - ["System", "String", False, "Concat", "(System.Object,System.Object)", "", "Argument[0,1]", "ReturnValue", "taint", "manual"]

此行定义了从第一个参数和第二个参数到返回值的流程。第七个值Argument[0,1]是指定对Argument[0]Argument[1]的访问路径的简写。

示例:通过Trim方法添加流程

此示例展示了 C# 查询包如何为简单情况对方法的流程进行建模。

public static void TaintFlow(string s) {
    string t = s.Trim(); // There is taint flow from s to t.
    ...
}

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

extensions:
  - addsTo:
      pack: codeql/csharp-all
      extensible: summaryModel
    data:
      - ["System", "String", False, "Trim", "()", "", "Argument[this]", "ReturnValue", "taint", "manual"]

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

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

  • 第一个值System是命名空间名称。
  • 第二个值String是类(类型)名称。
  • 第三个值False是一个标志,指示摘要是否也适用于该方法的所有覆盖。
  • 第四个值Trim是方法名称。
  • 第五个值 () 是方法输入类型签名。

第六个值应留空,并且不在本文档范围之内。其余值用于定义access pathkindprovenance(来源)摘要。

  • 第七个值是输入的访问路径(数据流出的位置)。Argument[this]是限定符(示例中的s)的访问路径。
  • 第八个值ReturnValue是输出的访问路径(数据流入的位置),在本例中为ReturnValue,这意味着输入流入返回值。
  • 第九个值taint是流程的类型。 taint表示污染通过调用传播。
  • 第十个值manual是摘要的来源,用于标识摘要的来源。

示例:通过Select方法添加流程

此示例展示了 C# 查询包如何对更复杂的流程进行建模。这里,我们对高阶方法和集合类型的流程进行建模,以及如何处理扩展方法和泛型。

public static void TaintFlow(IEnumerable<string> stream) {
  IEnumerable<string> lines = stream.Select(item => item + "\n");
  ...
}

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

extensions:
  - addsTo:
      pack: codeql/csharp-all
      extensible: summaryModel
    data:
      - ["System.Linq", "Enumerable", False, "Select<TSource,TResult>", "(System.Collections.Generic.IEnumerable<TSource>,System.Func<TSource,TResult>)", "", "Argument[0].Element", "Argument[1].Parameter[0]", "value", "manual"]
      - ["System.Linq", "Enumerable", False, "Select<TSource,TResult>", "(System.Collections.Generic.IEnumerable<TSource>,System.Func<TSource,TResult>)", "", "Argument[1].ReturnValue", "ReturnValue.Element", "value", "manual"]

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

  • 第一个值System.Linq是命名空间名称。
  • 第二个值Enumerable是类(类型)名称。
  • 第三个值False是一个标志,指示摘要是否也适用于该方法的所有覆盖。
  • 第四个值Select<TSource,TResult>是方法名称,以及该方法的类型参数。模型中提供的泛型类型参数的名称必须与源代码中方法签名中的泛型类型参数的名称匹配。
  • 第五个值(System.Collections.Generic.IEnumerable<TSource>,System.Func<TSource,TResult>)是方法输入类型签名。签名中的泛型必须与源代码中方法签名中的泛型匹配。

第六个值应留空,并且不在本文档范围之内。其余值用于定义access pathkindprovenance(来源)摘要定义。

  • 第七个值是input(数据流出的位置)的访问路径。
  • 第八个值是output(数据流入的位置)的访问路径。

对于第一行

  • 第七个值是Argument[0].Element,它是限定符元素的访问路径(示例中枚举的stream的元素)。
  • 第八个值是Argument[1].Parameter[0],它是SelectSystem.Func<TSource,TResult>参数的第一个参数的访问路径(示例中的 lambda 参数item)。

对于第二行

  • 第七个值是Argument[1].ReturnValue,它是SelectSystem.Func<TSource,TResult>参数的返回值的访问路径(示例中 lambda 的返回值)。
  • 第八个值是ReturnValue.Element,它是Select返回值元素的访问路径(示例中枚举的lines的元素)。

对于两行中其余值

  • 第九个值value是流程的类型。 value表示值得以保留。
  • 第十个值manual是摘要的来源,用于标识摘要的来源。

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

示例:添加neutral方法

此示例展示了我们如何将方法建模为相对于流程而言是中性的。我们还将介绍如何通过将DateTime类的Now属性的 getter 建模为中性来建模属性。中性模型用于定义方法没有流程。

public static void TaintFlow() {
    System.DateTime t = System.DateTime.Now; // There is no flow from Now to t.
    ...
}

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

extensions:
- addsTo:
    pack: codeql/csharp-all
    extensible: neutralModel
  data:
    - ["System", "DateTime", "get_Now", "()", "summary", "manual"]

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

  • 第一个值System是命名空间名称。
  • 第二个值DateTime是类(类型)名称。
  • 第三个值get_Now是方法名称。getter 方法和 setter 方法分别命名为get_<name>set_<name>
  • 第四个值()是方法输入类型签名。
  • 第五个值summary是中性的类型。
  • 第六个值manual是中性的来源。

威胁模型

注意

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

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

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

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

运行 CodeQL 分析时,默认情况下会包含 remote 威胁模型。使用 CodeQL CLI 和 GitHub 代码扫描时,可以根据需要选择性地包含其他威胁模型。有关更多信息,请参见 使用 CodeQL 查询分析代码自定义代码扫描的进阶设置

  • ©GitHub, Inc.
  • 条款
  • 隐私