CodeQL 文档

自定义 Ruby 库模型

Beta 通知 - 不稳定 API

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

此格式在 Beta 阶段可能会出现重大更改。

可以通过在数据扩展文件中添加库模型来自定义 Ruby 分析。

Ruby 的数据扩展是一个 YAML 文件,格式如下

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

CodeQL for Ruby 库公开了以下可扩展谓词

  • sourceModel(type, path, kind)
  • sinkModel(type, path, kind)
  • typeModel(type1, type2, path)
  • summaryModel(type, path, input, output, kind)

我们将通过几个示例来解释如何使用这些谓词,并在本文末尾提供一些参考资料。

示例:‘tty-command’ gem 中的污染接收器

在这个示例中,我们将展示如何将传递给 tty-command 的以下参数添加为命令行注入接收器

tty = TTY::Command.new
tty.run(cmd) # <-- add 'cmd' as a taint sink

对于此示例,您可以使用以下数据扩展

extensions:
  - addsTo:
      pack: codeql/ruby-all
      extensible: sinkModel
    data:
      - ["TTY::Command", "Method[run].Argument[0]", "command-injection"]
  • 由于我们正在添加一个新的接收器,因此我们在 sinkModel 可扩展谓词中添加了一个元组。
  • 第一列,“TTY::Command”,标识了一组值,从这些值开始搜索接收器。字符串 “TTY::Command”” 意味着我们从代码库构造 TTY::Command 类的实例的地方开始。
  • 第二列是一个访问路径,从左到右进行评估,从第一列标识的值开始。
    • Method[run] 选择对 TTY::Command 类的 run 方法的调用。
    • Argument[0] 选择对该成员的调用的第一个参数。
  • command-injection 表示这被认为是命令注入查询的接收器。

示例:来自 ‘sinatra’ 块参数的污染源

在这个示例中,我们将展示如何将下面的 ‘x’ 参数标记为远程流源

class MyApp < Sinatra::Base
  get '/' do |x| # <-- add 'x' as a taint source
    # ...
  end
end

对于此示例,您可以使用以下数据扩展

extensions:
  - addsTo:
      pack: codeql/ruby-all
      extensible: sourceModel
    data:
      - [
          "Sinatra::Base!",
          "Method[get].Argument[block].Parameter[0]",
          "remote",
        ]
  • 由于我们正在添加一个新的污染源,因此我们在 sourceModel 可扩展谓词中添加了一个元组。
  • 第一列,“Sinatra::Base!”,从对 Sinatra::Base 类的引用开始搜索。! 后缀表示我们想要搜索对类的引用本身,而不是类的实例。
  • Method[get] 选择对 Sinatra::Base 类的 get 方法的调用。
  • Argument[block] 选择对 get 方法调用的块参数。
  • Parameter[0] 选择块参数的第一个参数(名为 x 的参数)。
  • 最后,kind remote 表示这被认为是远程流的源。

示例:使用类型添加 MySQL 注入接收器

在这个示例中,我们将展示如何添加以下 SQL 注入接收器

def submit(q)
  client = Mysql2::Client.new
  client.query(q) # <-- add 'q' as a SQL injection sink
end

我们可以使用以下扩展来识别它

extensions:
  - addsTo:
      pack: codeql/ruby-all
      extensible: sinkModel
    data:
      - ["Mysql2::Client", "Method[query].Argument[0]", "sql-injection"]
  • 第一列,“Mysql2::Client”,从 Mysql2::Client 类的任何实例开始搜索。
  • Method[query] 选择对该实例的 query 方法的任何调用。
  • Argument[0] 选择方法调用的第一个参数。
  • sql-injection 表示这被认为是 SQL 注入查询的接收器。

示例继续:使用类型模型

考虑前面示例的这种变体,使用 mysql2 EventMachine API。客户端通过调用 Mysql2::EM::Client.new 获得。

def submit(client, q)
  client = Mysql2::EM::Client.new
  client.query(q)
end

到目前为止,我们只有一个 Mysql2::Client 模型,但在现实世界中,我们可能有很多针对各种可用方法的模型。因为 Mysql2::EM::ClientMysql2::Client 的子类,所以它继承了所有相同的方法。与其更新所有模型以包含这两个类,我们可以添加一个类型模型来表明 Mysql2::EM::ClientMysql2::Client 的子类

extensions:
  - addsTo:
      pack: codeql/ruby-all
      extensible: typeModel
    data:
      - ["Mysql2::Client", "Mysql2::EM::Client", ""]

示例:添加流经 ‘URI.decode_uri_component’

在这个示例中,我们将展示如何添加流经对 ‘URI.decode_uri_component’ 的调用。

y = URI.decode_uri_component(x); # add taint flow from 'x' to 'y'

我们可以使用以下数据扩展来对它进行建模

extensions:
  - addsTo:
      pack: codeql/ruby-all
      extensible: summaryModel
    data:
      - [
          "URI!",
          "Method[decode_uri_component]",
          "Argument[0]",
          "ReturnValue",
          "taint",
        ]
  • 由于我们正在添加流经方法调用的流,因此我们在 summaryModel 可扩展谓词中添加了一个元组。
  • 第一列,“URI!”,从对 URI 类的引用开始搜索相关调用。
  • ! 后缀表示我们正在寻找类本身,而不是类的实例。
  • 第二列,Method[decode_uri_component],是通往我们希望建模的方法调用的路径。在这种情况下,我们从 URI 类中选择对 decode_uri_component 方法的引用。
  • 第三列,Argument[0],表示流的输入。在这种情况下,方法调用的第一个参数。
  • 第四列,ReturnValue,表示流的输出。在这种情况下,方法调用的返回值。
  • 最后一列,taint,表示要添加的流的类型。值 taint 表示输出不一定等于输入,但以污染保留的方式从输入派生。

示例:添加流经 ‘File#each’

在这个示例中,我们将展示如何添加流经对标准库中的 File#each 的调用,它迭代文件中的每一行

f = File.new("example.txt")
f.each { |line| ... } # add taint flow from `f` to `line`

我们可以使用以下数据扩展来对它进行建模

extensions:
  - addsTo:
      pack: codeql/ruby-all
      extensible: summaryModel
    data:
      - [
          "File",
          "Method[each]",
          "Argument[self]",
          "Argument[block].Parameter[0]",
          "taint",
        ]
  • 由于我们正在添加流经方法调用的流,因此我们在 summaryModel 可扩展谓词中添加了一个元组。
  • 第一列,“File”,从使用 File 类的地方开始搜索相关调用。
  • 第二列,Method[each],选择对 File 类的 each 方法的引用。
  • 第三列指定了流的输入。Argument[self] 选择 eachself 参数,它是正在迭代的 File 实例。
  • 第四列指定了流的输出
    • Argument[block] 选择 each 的块参数(为文件中的每一行执行的块)。
    • Parameter[0] 选择块的第一个参数(名为 line 的参数)。
  • 最后一列,taint,表示要添加的流的类型。

参考资料

以下部分提供了可扩展谓词、访问路径、类型和类型的参考资料。

可扩展谓词

sourceModel(type, path, kind)

添加新的污染源。大多数污染跟踪查询将使用新的源。

  • type: 要评估 path 的类型的名称。
  • path: 导致源的访问路径。
  • kind: 要添加的源的类型。目前仅使用 remote

示例

extensions:
  - addsTo:
      pack: codeql/ruby-all
      extensible: sourceModel
    data:
      - ["User", "Method[name]", "remote"]

sinkModel(type, path, kind)

添加新的污染接收器。接收器是特定于查询的,通常会影响一个或两个查询。

  • type: 要评估 path 的类型的名称。
  • path: 导致接收器的访问路径。
  • kind: 要添加的接收器的类型。有关支持的类型的列表,请参阅接收器类型的部分。

示例

extensions:
  - addsTo:
      pack: codeql/ruby-all
      extensible: sinkModel
    data:
      - ["ExecuteShell", "Method[run].Argument[0]", "command-injection"]

summaryModel(type, path, input, output, kind)

添加流经方法调用的流。

  • type: 要评估 path 的类型的名称。
  • path: 导致方法调用的访问路径。
  • input: 相对于方法调用的路径,导致流的输入。
  • output: 相对于方法调用的路径,导致流的输出。
  • kind: 要添加的摘要的类型。可以是 taint,用于污染传播流,或 value,用于值保留流。

示例

extensions:
  - addsTo:
      pack: codeql/ruby-all
      extensible: summaryModel
    data:
      - [
          "URI",
          "Method[decode_uri_component]",
          "Argument[0]",
          "ReturnValue",
          "taint",
        ]

typeModel(type1, type2, path)

添加类型的新的定义。

  • type1: 要定义的类型的名称。
  • type2: 要评估 path 的类型的名称。
  • path: 从 type2type1 的访问路径。

示例

extensions:
- addsTo:
    pack: codeql/ruby-all
    extensible: typeModel
  data:
    - [
        "Mysql2::Client",
        "MyDbWrapper",
        "Method[getConnection].ReturnValue",
      ]

类型

类型是一个字符串,用于标识一组值。在前面部分提到的每个可扩展谓词中,第一列始终是类型的名称。可以通过为该类型添加 typeModel 元组来定义类型。

访问路径

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

支持以下组件

  • Argument[number] 选择给定索引处的参数。
  • Argument[string:] 选择具有给定名称的关键字参数。
  • Argument[self] 选择方法调用的接收方。
  • Argument[block] 选择块参数。
  • Argument[any] 选择任何参数,除了 self 或块参数。
  • Argument[any-named] 选择任何关键字参数。
  • Argument[hash-splat] 选择一个特殊参数,表示在方法调用中传递的所有关键字参数。
  • Parameter[number] 选择给定索引处的参数。
  • Parameter[string:] 选择具有给定名称的关键字参数。
  • Parameter[self] 选择方法的 self 参数。
  • Parameter[block] 选择块参数。
  • Parameter[any] 选择任何参数,除了 self 或块参数。
  • Parameter[any-named] 选择任何关键字参数。
  • Parameter[hash-splat] 选择散列 splat 参数,通常写为 **kwargs
  • ReturnValue 选择调用的返回值。
  • Method[name] 选择对具有给定名称的方法的调用。
  • Element[any] 选择数组或散列的任何元素。
  • Element[number] 选择给定索引处的数组元素。
  • Element[string] 选择给定键处的散列元素。
  • Field[@string] 选择具有给定名称的实例变量。
  • Fuzzy 选择通过此列表中描述的其他操作的组合从当前值派生的所有值。例如,这可以用于查找来自特定类的所有值,但接收方类型未知或难以建模。这对于查找来自已知类的函数调用很有用,但接收方类型未知或难以建模。

有关操作数语法的附加说明

  • 单个组件可以接收多个操作数,作为操作数并集的简写形式。例如,**Method[foo,bar]** 与 **Method[foo]** 和 **Method[bar]** 的并集匹配。
  • **Argument**、**Parameter** 和 **Element** 的数字操作数可以作为下限给出。例如,**Argument[1..]** 匹配除 0 之外的所有参数。

类型

源类型

  • **remote**:远程流的通用来源。大多数污点追踪查询将使用此类来源。目前这是唯一支持的源类型。

接收器类型

与源不同,接收器通常高度特定于查询,很少影响一个或两个以上的查询。并非所有查询都支持可定制的接收器。如果以下接收器不适合您的用例,您应该添加新的查询。

  • **code-injection**:一个可用于注入代码的接收器,例如在对 **eval** 的调用中。
  • **command-injection**:一个可用于注入 shell 命令的接收器,例如在对 **Process.spawn** 的调用中。
  • **path-injection**:一个可用于在文件系统访问中进行路径注入的接收器,例如在对 **File.open** 的调用中。
  • **sql-injection**:一个可用于进行 SQL 注入的接收器,例如在 ActiveRecord **where** 调用中。
  • **url-redirection**:一个可用于将用户重定向到恶意 URL 的接收器。
  • **log-injection**:一个可用于日志注入的接收器,例如在 **Rails.logger** 调用中。

摘要类型

  • **taint**:传播污点的摘要。这意味着输出不一定等于输入,但它是通过不受限制的方式从输入派生的。控制输入的攻击者也将对输出有很大的控制权。
  • **value**:保留输入的值或创建输入的副本,以使所有其对象属性都得到保留的摘要。
  • ©GitHub, Inc.
  • 条款
  • 隐私