为 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
定义一个或多个元组行,这些元组行被注入为可扩展谓词中的值。列数及其类型必须与可扩展谓词的定义匹配。
数据扩展使用联合语义,这意味着为单个可扩展谓词组合所有扩展的元组,删除重复项,所有剩余的元组都可以通过引用可扩展谓词来查询。
用于在 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)
。这类似于摘要模型,但用于对对数据流分析影响很小的值的流进行建模。手动中性模型(具有诸如manual
或ai-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 path
、kind
和 provenance
(来源)。
- 第七个值
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 path
、kind
和 provenance
(来源)。
- 第七个值
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 path
、kind
和provenance
(来源)摘要。
- 第七个值是输入的访问路径(数据流出的位置)。
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 path
、kind
和provenance
(来源)摘要。
- 第七个值是输入的访问路径(数据流出的位置)。
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 path
、kind
和provenance
(来源)摘要定义。
- 第七个值是
input
(数据流出的位置)的访问路径。 - 第八个值是
output
(数据流入的位置)的访问路径。
对于第一行
- 第七个值是
Argument[0].Element
,它是限定符元素的访问路径(示例中枚举的stream
的元素)。 - 第八个值是
Argument[1].Parameter[0]
,它是Select
的System.Func<TSource,TResult>
参数的第一个参数的访问路径(示例中的 lambda 参数item
)。
对于第二行
- 第七个值是
Argument[1].ReturnValue
,它是Select
的System.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 分析中使用相关的污染源。
sourceModel
的 kind
属性决定了源关联的威胁模型。主要有两类:
remote
代表来自网络的请求和响应。local
代表来自本地文件 (file
)、命令行参数 (commandargs
)、数据库读取 (database
) 和环境变量 (environment
) 的数据。
运行 CodeQL 分析时,默认情况下会包含 remote
威胁模型。使用 CodeQL CLI 和 GitHub 代码扫描时,可以根据需要选择性地包含其他威胁模型。有关更多信息,请参见 使用 CodeQL 查询分析代码 和 自定义代码扫描的进阶设置。