CodeQL 文档

在 Ruby 中使用 API 图

API 图是一种统一的接口,用于引用外部库中定义的函数、类和方法。

关于本文

本文介绍了如何使用 API 图来引用库代码中定义的类和函数。当您想要对外部库函数提供的远程流源进行建模时,API 图特别有用。

模块和类引用

进入 API 图的最常见入口点是访问顶级模块或类。例如,您可以使用 codeql.ruby.ApiGraphs 模块中定义的 API::getTopLevelMember 方法来访问与 ::Regexp 类对应的 API 图节点,如下面的代码片段所示。

import codeql.ruby.ApiGraphs

select API::getTopLevelMember("Regexp")

上面的示例查找对顶级类的引用。对于嵌套模块和类,可以使用 getMember 方法。例如,以下查询选择对 Net::HTTP 类的引用。

import codeql.ruby.ApiGraphs

select API::getTopLevelMember("Net").getMember("HTTP")

请注意,您应该在没有 :: 符号的情况下指定模块名称。如果您写 API::getTopLevelMember("Net::HTTP"),它将不会像您预期的那样工作。相反,您需要将此名称分解为对 Net 的 API 图节点的 HTTP 成员的访问,如上面的示例所示。

调用和类实例化

要跟踪外部定义函数的调用,可以使用 getMethod 方法。以下代码片段查找 Regexp.compile 的所有调用

import codeql.ruby.ApiGraphs

select API::getTopLevelMember("Regexp").getMethod("compile")

上面的示例用于调用类方法。跟踪对实例方法的调用是一个两步过程,首先您需要找到类的实例,然后才能找到对这些实例上的方法的调用。以下代码片段查找 Regexp 类的实例化

import codeql.ruby.ApiGraphs

select API::getTopLevelMember("Regexp").getInstance()

请注意,getInstance 方法还包括子类。例如,如果有一个 class SpecialRegexp < Regexp,那么 getInstance 也能找到 SpecialRegexp.new

以下代码片段在上面基础上构建,查找 Regexp#match? 实例方法的调用

import codeql.ruby.ApiGraphs

select API::getTopLevelMember("Regexp").getInstance().getMethod("match?")

子类

许多库通过扩展一个或多个库类来使用。为了在 API 图中跟踪这一点,可以使用 getASubclass 方法来获取与节点的直接子类对应的 API 图节点。要查找所有子类,请使用 *+ 来重复应用该方法。您可以在下面看到一个使用 getASubclass* 标识所有子类的示例。

请注意,getASubclass 只能返回作为您正在分析的 CodeQL 数据库的一部分提取的子类。当库具有预定义的子类时,您需要在模型中明确包含它们。例如,ActionController::Base 类具有预定义的子类 Rails::ApplicationController。要查找 ActionController::Base 的所有子类,您还必须明确包含 Rails::ApplicationController 的子类。

import codeql.ruby.ApiGraphs


API::Node actionController() {
  result =
    [
      API::getTopLevelMember("ActionController").getMember("Base"),
      API::getTopLevelMember("Rails").getMember("ApplicationController")
    ].getASubclass*()
}

select actionController()

在数据流查询中使用 API 图

数据流查询通常搜索数据从外部源进入代码库的点,以及数据离开代码库的点。API 图提供了一种方便的方式来引用外部 API 组件,例如库函数及其输入和输出。但是,您不会在数据流查询中直接使用 API 图节点。

  • API 图节点对在代码库之外定义的实体进行建模。
  • 数据流节点对在当前代码库中定义的实体进行建模。

您使用 API 节点类方法:asSource()asSink() 来弥合代码库内外实体之间的差距。

asSource() 方法用于选择数据从外部源进入当前代码库的值所在的数据流节点。一个典型的例子是库函数的返回值,例如 File.read(path)

import codeql.ruby.ApiGraphs

select API::getTopLevelMember("File").getMethod("read").getReturn().asSource()

asSink() 方法用于选择数据离开当前代码库并流入外部库的值所在的数据流节点。例如,File.write(path, value) 方法的第二个参数。

import codeql.ruby.ApiGraphs

select API::getTopLevelMember("File").getMethod("write").getParameter(1).asSink()

一个更复杂的例子是对 File.open 的调用,带有块参数。此函数创建一个 File 实例并将其传递给提供的块。在这种情况下,我们对块的第一个参数感兴趣,因为这是外部创建的值进入代码库的地方,即下面 Ruby 示例中的 |file|

File.open("/my/file.txt", "w") { |file| file << "Hello world" }

以下 CodeQL 代码片段查找 File.open 方法调用的块的参数

import codeql.ruby.ApiGraphs

select API::getTopLevelMember("File").getMethod("open").getBlock().getParameter(0).asSource()

以下示例是一个数据流查询,它使用 API 图来查找读取的数据流入对 File.write 的调用的情况。

import codeql.ruby.DataFlow
import codeql.ruby.ApiGraphs

module Configuration implements DataFlow::ConfigSig {
  predicate isSource(DataFlow::Node source) {
    source = API::getTopLevelMember("File").getMethod("read").getReturn().asSource()
  }

  predicate isSink(DataFlow::Node sink) {
    sink = API::getTopLevelMember("File").getMethod("write").getParameter(1).asSink()
  }
}

module Flow = DataFlow::Global<Configuration>;

from DataFlow::Node src, DataFlow::Node sink
where Flow::flow(src, sink)
select src, "The data read here flows into a $@ call.", sink, "File.write"
  • ©GitHub, Inc.
  • 条款
  • 隐私