为 Python 库模型定制¶
Beta 通知 - 不稳定 API
使用数据扩展进行库定制目前处于测试阶段,可能会发生变化。
此格式在测试阶段可能会出现重大更改。
可以通过在数据扩展文件中添加库模型来定制 Python 分析。
Python 的数据扩展是一个形式为 YAML 文件
extensions:
- addsTo:
pack: codeql/python-all
extensible: <name of extensible predicate>
data:
- <tuple1>
- <tuple2>
- ...
CodeQL 库 for Python 公开了以下可扩展谓词
- sourceModel(type, path, kind)
- sinkModel(type, path, kind)
- typeModel(type1, type2, path)
- summaryModel(type, path, input, output, kind)
我们将解释如何使用这些谓词,并提供一些参考材料在本文章末尾。
示例:‘fabric’ 包中的污点接收器¶
在本例中,我们将展示如何将以下参数(传递给 fabric 包中的 sudo)添加为命令行注入接收器
from fabric.operations import sudo
sudo(cmd) # <-- add 'cmd' as a taint sink
请注意,此接收器已被 CodeQL Python 分析识别,但在此示例中,您可以使用以下数据扩展
extensions:
- addsTo:
pack: codeql/python-all
extensible: sinkModel
data:
- ["fabric", "Member[operations].Member[sudo].Argument[0]", "command-injection"]
- 由于我们正在添加一个新的接收器,因此我们在 sinkModel 可扩展谓词中添加了一个元组。
- 第一列 “fabric” 标识了一组值,从这些值开始搜索接收器。字符串 “fabric” 表示我们从代码库导入 fabric 包的地方开始。
- 第二列是访问路径,它从左到右进行评估,从由第一列标识的值开始。
- Member[operations] 选择对 operations 模块的访问。
- Member[sudo] 选择对 operations 模块中的 sudo 函数的访问。
- Argument[0] 选择对该函数调用的第一个参数。
- “command-injection” 表示这被认为是命令注入查询的接收器。
示例:‘invoke’ 包中的污点接收器¶
接收器通常作为方法的参数而不是函数的参数。在本例中,我们将展示如何将以下参数(传递给 invoke 包中的 run)添加为命令行注入接收器
import invoke
c = invoke.Context()
c.run(cmd) # <-- add 'cmd' as a taint sink
请注意,此接收器已被 CodeQL Python 分析识别,但在此示例中,您可以使用以下数据扩展
extensions:
- addsTo:
pack: codeql/python-all
extensible: sinkModel
data:
- ["invoke", "Member[Context].Instance.Member[run].Argument[0]", "command-injection"]
- 第一列 “invoke” 从代码库导入 invoke 包的地方开始搜索。
- 第二列是访问路径,它从左到右进行评估,从由第一列标识的值开始。
- Member[Context] 选择对 Context 类的访问。
- Instance 选择 Context 类的实例。
- Member[run] 选择对 Context 类中的 run 方法的访问。
- Argument[0] 选择对该方法调用的第一个参数。
- “command-injection” 表示这被认为是命令注入查询的接收器。
请注意,Instance 组件用于选择类的实例,包括其子类的实例。由于实例上的方法是常见的目标,因此我们有更简洁的语法来选择它们。第一列(类型)允许包含以类名结尾的点分路径。这将从该类的实例开始搜索。使用这种语法,前面的示例可以写成
extensions:
- addsTo:
pack: codeql/python-all
extensible: sinkModel
data:
- ["invoke.Context", "Member[run].Argument[0]", "command-injection"]
继续示例:获取类型的多种方式¶
invoke 包提供了多种获取 Context 实例的方式。以下示例展示了如何添加一种新的获取 Context 实例的方式
from invoke import context
c = context.Context()
c.run(cmd) # <-- add 'cmd' as a taint sink
与前面的 Python 代码片段相比,Context 类现在被找到为 invoke.context.Context 而不是 invoke.Context。我们可以添加一个类似于前面的数据扩展,但类型为 invoke.context.Context。但是,我们也可以使用 typeModel 可扩展谓词来描述如何从 invoke.context.Context 获取 invoke.Context
extensions:
- addsTo:
pack: codeql/python-all
extensible: typeModel
data:
- ["invoke.Context", "invoke.context.Context", ""]
- 第一列 “invoke.Context” 是要到达的类型的名称。
- 第二列 “invoke.context.Context” 是从其评估路径的类型的名称。
- 第三列只是一个空字符串,表示 invoke.context.Context 的任何实例也是 invoke.Context 的实例。
将此与我们之前添加的接收器模型结合起来,示例中的接收器会被模型检测到。
示例:来自 Django ‘upload_to’ 参数的污点源¶
此示例更高级一些,涉及回调函数和类构造函数。Django Web 框架允许您指定一个函数来确定上传文件存储的路径(请参阅 Django 文档)。此函数作为参数传递给 FileField 构造函数。该函数用两个参数调用:模型的实例和上传文件的名称。我们想将此文件名标记为污点源。示例用法如下所示
from django.db import models
def user_directory_path(instance, filename): # <-- add 'filename' as a taint source
# file will be uploaded to MEDIA_ROOT/user_<id>/<filename>
return "user_{0}/{1}".format(instance.user.id, filename)
class MyModel(models.Model):
upload = models.FileField(upload_to=user_directory_path) # <-- the 'upload_to' parameter defines our custom function
请注意,此源已被 CodeQL Python 分析识别,但在此示例中,您可以使用以下数据扩展
extensions:
- addsTo:
pack: codeql/python-all
extensible: sourceModel
data:
- [
"django.db.models.FileField!",
"Call.Argument[0,upload_to:].Parameter[1]",
"remote",
]
由于我们正在添加一个新的污点源,因此我们在 sourceModel 可扩展谓词中添加了一个元组。
第一列 “django.db.models.FileField!” 是从 django.db.models 包获取 FileField 类的点分路径。类型名末尾的 ! 表示我们正在查找类本身,而不是该类的实例。
第二列是访问路径,它从左到右进行评估,从由第一列标识的值开始。
- Call 选择对该类的调用。即构造函数调用。
- Argument[0,upload_to:] 选择第一个位置参数,或名为 upload_to 的命名参数。请注意,参数名称末尾的冒号表示我们正在查找命名参数。
- Parameter[1] 选择回调函数的第二个参数,即接收文件名的参数。
最后,kind “remote” 表示这被认为是远程流的源。
示例:添加通过 ‘re.compile’ 的流¶
在本例中,我们将展示如何添加通过对 re.compile
的调用的流。 re.compile
返回一个编译后的正则表达式,用于高效评估,但要编译的模式存储在结果对象的 pattern
属性中。
import re
let y = re.compile(pattern = x); // add value flow from 'x' to 'y.pattern'
请注意,此流已被 CodeQL Python 分析识别,但在此示例中,您可以使用以下数据扩展
extensions:
- addsTo:
pack: codeql/python-all
extensible: summaryModel
data:
- [
"re",
"Member[compile]",
"Argument[0,pattern:]",
"ReturnValue.Attribute[pattern]",
"value",
]
- 由于我们正在添加一个通过函数调用的流,因此我们在 summaryModel 可扩展谓词中添加了一个元组。
- 第一列 “re” 从导入 re 包的地方开始搜索相关调用。
- 第二列 “Member[compile]” 是一个路径,它指向我们希望建模的函数调用。在本例中,我们选择来自
re
包的 compile 函数的引用。 - 第三列 “Argument[0,pattern:]” 指示流的输入。在本例中,是函数调用的第一个参数,或者名为 pattern 的参数。
- 第四列 “ReturnValue.Attribute[pattern]” 指示流的输出。在本例中,是函数调用返回值的
pattern
属性。 - 最后一列 “value” 指示要添加的流类型。值 value 表示输入值在流到输出时保持不变。
示例:添加通过 ‘sorted’ 的流¶
在本例中,我们将展示如何添加通过对内置函数 sorted 的调用的流
y = sorted(x) # add taint flow from 'x' to 'y'
请注意,此流已被 CodeQL Python 分析识别,但在此示例中,您可以使用以下数据扩展
extensions:
- addsTo:
pack: codeql/python-all
extensible: summaryModel
data:
- [
"builtins",
"Member[sorted]",
"Argument[0]",
"ReturnValue",
"taint",
]
- 由于我们正在添加一个通过函数调用的流,因此我们在 summaryModel 可扩展谓词中添加了一个元组。
- 第一列 “builtins” 从对内置名称的引用中开始搜索相关调用。在 Python 中,许多内置函数可用。从技术上讲,这些函数中的大多数都是 builtins 包的一部分,但可以无需显式导入即可访问。当我们在第一列中写入 builtins 时,我们将找到对内置函数的隐式和显式引用。
- 第二列 “Member[sorted]” 选择来自 builtins 包的 sorted 函数的引用;即内置函数 sorted。
- 第三列 “Argument[0]” 指示流的输入。在本例中,是函数调用的第一个参数。
- 第四列 “ReturnValue” 指示流的输出。在本例中,是函数调用的返回值。
- 最后一列 “taint” 指示要添加的流类型。值 taint 表示输出不一定等于输入,但它是以保持污点的方式从输入派生的。
我们还可以提供一个摘要,说明输入列表中的元素保留在输出列表中
extensions:
- addsTo:
pack: codeql/python-all
extensible: summaryModel
data:
- [
"builtins",
"Member[sorted]",
"Argument[0].ListElement",
"ReturnValue.ListElement",
"value",
]
对列表元素的跟踪是不精确的,因为分析不知道在列表中哪个位置找到跟踪的值。因此,这个摘要只是说明,如果在输入列表中找到了该值,那么它也会在输出列表中找到,并且保持不变。
参考材料¶
以下部分提供了可扩展谓词、访问路径、类型和类型的参考材料。
可扩展谓词¶
sourceModel(type, path, kind)¶
添加一个新的污点源。大多数污点跟踪查询将使用新的源。
- type: 从其评估 path 的类型的名称。
- path: 指向源的访问路径。
- kind: 要添加的源类型。目前仅使用 remote。
示例
extensions:
- addsTo:
pack: codeql/python-all
extensible: sourceModel
data:
- ["flask", "Member[request]", "remote"]
sinkModel(type, path, kind)¶
添加一个新的污点接收器。接收器是特定于查询的,通常会影响一两个查询。
- type: 从其评估 path 的类型的名称。
- path: 指向接收器的访问路径。
- kind: 要添加的接收器类型。请参阅有关接收器类型的部分,了解支持的类型的列表。
示例
extensions:
- addsTo:
pack: codeql/python-all
extensible: sinkModel
data:
- ["builtins", "Member[exec].Argument[0]", "code-injection"]
summaryModel(type, path, input, output, kind)¶
添加通过函数调用的流。
- type: 从其评估 path 的类型的名称。
- path: 指向函数调用的访问路径。
- input: 相对于函数调用的路径,指向流的输入。
- output: 相对于函数调用的路径,指向流的输出。
- kind: 要添加的摘要类型。可以是 taint(用于传播污点的流),或 value(用于保持值的流)。
示例
extensions:
- addsTo:
pack: codeql/python-all
extensible: summaryModel
data:
- [
"builtins",
"Member[reversed]",
"Argument[0]",
"ReturnValue",
"taint",
]
typeModel(type1, type2, path)¶
描述如何从 type2 访问 type1。 如果这是访问 type1 的唯一方法,例如,如果 type1 是我们为表示库内部工作机制而编造的名称,那么我们认为这是一个 type1 的定义。 在实例的上下文中,这描述了如何从 type2 的实例获取 type1 的实例。
- type1:要访问的类型的名称。
- type2:用于评估 path 的类型的名称。
- path:从 type2 到 type1 的访问路径。
示例
extensions:
- addsTo:
pack: codeql/python-all
extensible: typeModel
data:
- [
"flask.Response",
"flask",
"Member[jsonify].ReturnValue",
]
类型¶
类型是一个字符串,用于标识一组值。 在上一节中提到的每个可扩展谓词中,第一列始终是类型的名称。 可以通过为该类型添加 typeModel 元组来定义类型。 此外,还提供了以下内置类型。
- 包的名称与该包的导入匹配。 例如,类型 django 匹配表达式 import django。
- 类型 builtins 标识内置包。 在 Python 中,所有内置值都位于此包中,因此可以使用此类型识别它们。
- 以类名结尾的点分路径标识该类的实例。 如果添加后缀 !,则类型指的是类本身。
访问路径¶
path、input 和 output 列由一个 . 分隔的组件列表组成,该列表从左到右进行评估,每一步都选择从先前的一组值派生的新的一组值。
支持以下组件
- Argument[
number
] 选择给定索引处的参数。 - Argument[
name
:] 选择具有给定名称的参数。 - Argument[this] 选择方法调用的接收者。
- Parameter[
number
] 选择给定索引处的参数。 - Parameter[
name
:] 选择具有给定名称的命名参数。 - Parameter[this] 选择函数的 this 参数。
- ReturnValue 选择函数或调用的返回值。
- Member[
name
] 选择具有给定名称的函数/方法/类/值。 - Instance 选择类的实例,包括其子类的实例。
- Attribute[
name
] 选择具有给定名称的属性。 - ListElement 选择列表的元素。
- SetElement 选择集合的元素。
- TupleElement[
number
] 选择给定索引处的下标。 - DictionaryElement[
name
] 选择给定名称处的下标。
关于操作数语法的其他说明
- 可以将多个操作数提供给单个组件,作为操作数联合的简写。 例如,Member[foo,bar] 匹配 Member[foo] 和 Member[bar] 的联合。
- Argument、Parameter 和 WithArity 的数字操作数可以以区间的形式给出。 例如,Argument[0..2] 匹配参数 0、1 或 2。
- Argument[N-1] 选择调用中的最后一个参数,而 Parameter[N-1] 选择函数中的最后一个参数,其中 N-2 为倒数第二个,依此类推。
种类¶
源种类¶
- remote:远程流的通用源。 大多数污点跟踪查询将使用此类源。 目前,这是唯一支持的源种类。
汇点种类¶
与源不同,汇点往往高度特定于查询,很少影响一个或两个以上的查询。 并非每个查询都支持可自定义的汇点。 如果以下汇点不适合您的用例,您应该添加一个新的查询。
- code-injection:可用于注入代码的汇点,例如在调用 exec 时。
- command-injection:可用于注入 shell 命令的汇点,例如在调用 os.system 时。
- path-injection:可用于在文件系统访问中注入路径的汇点,例如在调用 flask.send_from_directory 时。
- sql-injection:可用于 SQL 注入的汇点,例如在 MySQL query 调用中。
- html-injection:可用于 HTML 注入的汇点,例如服务器响应主体。
- js-injection:可用于 JS 注入的汇点,例如服务器响应主体。
- url-redirection:可用于将用户重定向到恶意 URL 的汇点。
- unsafe-deserialization:可导致代码执行或其他不安全行为的解序列化汇点,例如不安全的 YAML 解析器。
- log-injection:可用于日志注入的汇点,例如在 logging.info 调用中。
摘要种类¶
- taint:传播污点的摘要。 这意味着输出不一定等于输入,但它是以不受限制的方式从输入派生的。 能够控制输入的攻击者将对输出有很大的控制权。
- value:保留输入的值或创建输入的副本的摘要,以便保留其所有对象属性。