CodeQL 文档

在 Swift 中分析数据流

您可以使用 CodeQL 追踪数据在 Swift 程序中的流动情况,直到数据被使用的位置。

注意

CodeQL 对 Swift 的分析目前处于测试阶段。在测试阶段,对 Swift 代码的分析以及相关的文档可能不如其他语言那样全面。

关于本文

本文介绍了数据流分析在 CodeQL 的 Swift 库中的实现方式,并包含一些示例帮助您编写自己的数据流查询。以下部分介绍了如何使用库进行本地数据流、全局数据流和污点跟踪。有关数据流建模的更一般介绍,请参阅“关于数据流分析”。

本地数据流

本地数据流跟踪数据在单个函数中的流动情况。本地数据流比全局数据流更简单、更快,也更精确。在查看更复杂的数据流跟踪之前,您应该始终考虑本地数据流跟踪,因为它对于许多查询来说已经足够了。

使用本地数据流

您可以通过导入 DataFlow 模块来使用本地数据流库。该库使用类 Node 来表示任何数据流经的元素。Node 类包含许多有用的子类,例如 ExprNode 用于表达式,而 ParameterNode 用于参数。您可以使用成员谓词 asExprgetCfgNode 在数据流节点和表达式/控制流节点之间进行映射。

class Node {
  /**
   * Gets the expression that corresponds to this node, if any.
   */
  Expr asExpr() { ... }

  /**
   * Gets the control flow node that corresponds to this data flow node.
   */
  ControlFlowNode getCfgNode() { ... }

  ...
}

您可以使用谓词 exprNodeparameterNode 从表达式和参数映射到它们的数据流节点。

/**
 * Gets a node corresponding to expression `e`.
 */
ExprNode exprNode(DataFlowExpr e) { result.asExpr() = e }

/**
 * Gets the node corresponding to the value of parameter `p` at function entry.
 */
ParameterNode parameterNode(DataFlowParameter p) { result.getParameter() = p }

AST 中单个表达式节点可以关联多个数据流节点。

谓词 localFlowStep(Node nodeFrom, Node nodeTo) 如果从节点 nodeFrom 到节点 nodeTo 存在直接数据流边,则该谓词成立。您可以使用 +* 运算符递归地应用谓词,或者使用预定义的递归谓词 localFlow

例如,您可以找到从表达式 source 到表达式 sink 的零个或多个本地步骤的数据流。

DataFlow::localFlow(DataFlow::exprNode(source), DataFlow::exprNode(sink))

使用本地污点跟踪

本地污点跟踪扩展了本地数据流,以包含其中值未被保留的流步骤,例如字符串操作。例如

temp = x
y = temp + ", " + temp

如果 x 是一个受污染的字符串,则 y 也是受污染的。

本地污点跟踪库位于 TaintTracking 模块中。与本地数据流类似,谓词 localTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) 如果从节点 nodeFrom 到节点 nodeTo 存在直接污点传播边,则该谓词成立。您可以使用 +* 运算符递归地应用谓词,或者使用预定义的递归谓词 localTaint

例如,您可以找到从表达式 source 到表达式 sink 的零个或多个本地步骤的污点传播。

TaintTracking::localTaint(DataFlow::exprNode(source), DataFlow::exprNode(sink))

本地数据流示例

此查询查找传递给每个 String.init(format:_:) 调用的 format 参数。

import swift

from CallExpr call, Method method
where
  call.getStaticTarget() = method and
  method.hasQualifiedName("String", "init(format:_:)")
select call.getArgument(0).getExpr()

不幸的是,这只会给出参数中的表达式,而不是可能传递给它的值。因此,我们使用本地数据流来查找流入参数的所有表达式。

import swift
import codeql.swift.dataflow.DataFlow

from CallExpr call, Method method, Expr sourceExpr, Expr sinkExpr
where
  call.getStaticTarget() = method and
  method.hasQualifiedName("String", "init(format:_:)") and
  sinkExpr = call.getArgument(0).getExpr() and
  DataFlow::localFlow(DataFlow::exprNode(sourceExpr), DataFlow::exprNode(sinkExpr))
select sourceExpr, sinkExpr

我们可以更改源,例如,使源成为函数的参数而不是表达式。以下查询查找参数用于格式的位置。

import swift
import codeql.swift.dataflow.DataFlow

from CallExpr call, Method method, ParamDecl sourceParam, Expr sinkExpr
where
  call.getStaticTarget() = method and
  method.hasQualifiedName("String", "init(format:_:)") and
  sinkExpr = call.getArgument(0).getExpr() and
  DataFlow::localFlow(DataFlow::parameterNode(sourceParam), DataFlow::exprNode(sinkExpr))
select sourceParam, sinkExpr

以下示例查找 String.init(format:_:) 的调用,其中格式字符串不是硬编码的字符串文字。

import swift
import codeql.swift.dataflow.DataFlow

from CallExpr call, Method method, DataFlow::Node sinkNode
where
  call.getStaticTarget() = method and
  method.hasQualifiedName("String", "init(format:_:)") and
  sinkNode.asExpr() = call.getArgument(0).getExpr() and
  not exists(StringLiteralExpr sourceLiteral |
    DataFlow::localFlow(DataFlow::exprNode(sourceLiteral), sinkNode)
  )
select call, "Format argument to " + method.getName() + " isn't hard-coded."

全局数据流

全局数据流跟踪整个程序的数据流,因此比本地数据流更强大。但是,全局数据流不如本地数据流精确,并且分析通常需要更多时间和内存来执行。

注意

您可以通过创建路径查询在 CodeQL 中对数据流路径进行建模。要查看 CodeQL for VS Code 中路径查询生成的数据流路径,您需要确保它具有正确的元数据和 select 子句。有关更多信息,请参阅 创建路径查询

使用全局数据流

您可以通过实现模块 DataFlow::ConfigSig 来使用全局数据流库。

import codeql.swift.dataflow.DataFlow

module MyDataFlowConfiguration implements DataFlow::ConfigSig {
  predicate isSource(DataFlow::Node source) {
    ...
  }

  predicate isSink(DataFlow::Node sink) {
    ...
  }
}

module MyDataFlow = DataFlow::Global<MyDataFlowConfiguration>;

这些谓词在配置中定义。

  • isSource - 定义数据可能从哪里流出。
  • isSink - 定义数据可能流入哪里。
  • isBarrier - 可选,限制数据流。
  • isAdditionalFlowStep - 可选,添加额外的流步骤。

最后一行(module MyDataFlow = ...)通过将配置传递给参数化模块来实例化数据流分析的参数化模块。然后可以使用 MyDataFlow::flow(DataFlow::Node source, DataFlow::Node sink) 执行数据流分析。

from DataFlow::Node source, DataFlow::Node sink
where MyDataFlow::flow(source, sink)
select source, "Dataflow to $@.", sink, sink.toString()

使用全局污点跟踪

全局污点跟踪与全局数据流之间的关系,就像本地污点跟踪与本地数据流之间的关系一样。也就是说,全局污点跟踪通过额外的非值保留步骤扩展了全局数据流。全局污点跟踪库使用与全局数据流库相同的配置模块。您可以使用 TaintTracking::Global 执行污点流分析。

module MyTaintFlow = TaintTracking::Global<MyDataFlowConfiguration>;

from DataFlow::Node source, DataFlow::Node sink
where MyTaintFlow::flow(source, sink)
select source, "Taint flow to $@.", sink, sink.toString()

预定义的源

数据流库模块 codeql.swift.dataflow.FlowSources 包含许多预定义的源,您可以使用它们来编写安全查询以跟踪数据流和污点流。

  • RemoteFlowSource 表示来自远程网络输入和来自其他应用程序的数据流。
  • LocalFlowSource 表示来自本地用户输入的数据流。
  • FlowSource 包含上述两者。

全局数据流示例

以下全局污点跟踪查询查找字符串文字在名为“password”的函数调用参数中使用的位置。
  • 由于这是一个污点跟踪查询,因此使用 TaintTracking::Global 模块。
  • 谓词 isSource 将源定义为任何 StringLiteralExpr
  • 谓词 isSink 将接收器定义为名为“password”的 CallExpr 的参数。
  • 源和接收器可能需要根据特定用途进行调整,例如,如果密码由除 String 以外的类型表示,或者通过除“password”以外的其他名称的参数传递。
import swift
import codeql.swift.dataflow.DataFlow
import codeql.swift.dataflow.TaintTracking

module ConstantPasswordConfig implements DataFlow::ConfigSig {
  predicate isSource(DataFlow::Node node) { node.asExpr() instanceof StringLiteralExpr }

  predicate isSink(DataFlow::Node node) {
    // any argument called `password`
    exists(CallExpr call | call.getArgumentWithLabel("password").getExpr() = node.asExpr())
  }

module ConstantPasswordFlow = TaintTracking::Global<ConstantPasswordConfig>;

from DataFlow::Node sourceNode, DataFlow::Node sinkNode
where ConstantPasswordFlow::flow(sourceNode, sinkNode)
select sinkNode, "The value $@ is used as a constant password.", sourceNode, sourceNode.toString()
以下全局污点跟踪查询查找来自远程或本地用户输入的值用作 SQLite Connection.execute(_:) 函数的参数的位置。
  • 由于这是一个污点跟踪查询,因此使用 TaintTracking::Global 模块。
  • 谓词 isSource 将源定义为 FlowSource(远程或本地用户输入)。
  • 谓词 isSink 将接收器定义为对 Connection.execute(_:) 的任何调用的第一个参数。
import swift
import codeql.swift.dataflow.DataFlow
import codeql.swift.dataflow.TaintTracking
import codeql.swift.dataflow.FlowSources

module SqlInjectionConfig implements DataFlow::ConfigSig {
  predicate isSource(DataFlow::Node node) { node instanceof FlowSource }

  predicate isSink(DataFlow::Node node) {
    exists(CallExpr call |
      call.getStaticTarget().(Method).hasQualifiedName("Connection", "execute(_:)") and
      call.getArgument(0).getExpr() = node.asExpr()
    )
  }
}

module SqlInjectionFlow = TaintTracking::Global<SqlInjectionConfig>;

from DataFlow::Node sourceNode, DataFlow::Node sinkNode
where SqlInjectionFlow::flow(sourceNode, sinkNode)
select sinkNode, "This query depends on a $@.", sourceNode, "user-provided value"
  • ©GitHub, Inc.
  • 条款
  • 隐私