自定义 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::Client 是 Mysql2::Client 的子类,所以它继承了所有相同的方法。与其更新所有模型以包含这两个类,我们可以添加一个类型模型来表明 Mysql2::EM::Client 是 Mysql2::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] 选择 each 的 self 参数,它是正在迭代的 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: 从 type2 到 type1 的访问路径。
示例
extensions:
- addsTo:
pack: codeql/ruby-all
extensible: typeModel
data:
- [
"Mysql2::Client",
"MyDbWrapper",
"Method[getConnection].ReturnValue",
]
类型¶
类型是一个字符串,用于标识一组值。在前面部分提到的每个可扩展谓词中,第一列始终是类型的名称。可以通过为该类型添加 typeModel 元组来定义类型。
访问路径¶
path、input 和 output 列包含一个以 . 分隔的组件列表,该列表从左到右进行评估,每一步都会选择从前一组值派生的一组新值。
支持以下组件
- 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**:保留输入的值或创建输入的副本,以使所有其对象属性都得到保留的摘要。