CodeQL 文档

为 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:从 type2type1 的访问路径。

示例

extensions:
- addsTo:
    pack: codeql/python-all
    extensible: typeModel
  data:
    - [
        "flask.Response",
        "flask",
        "Member[jsonify].ReturnValue",
      ]

类型

类型是一个字符串,用于标识一组值。 在上一节中提到的每个可扩展谓词中,第一列始终是类型的名称。 可以通过为该类型添加 typeModel 元组来定义类型。 此外,还提供了以下内置类型。

  • 包的名称与该包的导入匹配。 例如,类型 django 匹配表达式 import django
  • 类型 builtins 标识内置包。 在 Python 中,所有内置值都位于此包中,因此可以使用此类型识别它们。
  • 以类名结尾的点分路径标识该类的实例。 如果添加后缀 !,则类型指的是类本身。

访问路径

pathinputoutput 列由一个 . 分隔的组件列表组成,该列表从左到右进行评估,每一步都选择从先前的一组值派生的新的一组值。

支持以下组件

  • 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] 的联合。
  • ArgumentParameterWithArity 的数字操作数可以以区间的形式给出。 例如,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:保留输入的值或创建输入的副本的摘要,以便保留其所有对象属性。
  • ©GitHub, Inc.
  • 条款
  • 隐私