在 Swift 中分析数据流¶
您可以使用 CodeQL 追踪数据在 Swift 程序中的流动情况,直到数据被使用的位置。
注意
CodeQL 对 Swift 的分析目前处于测试阶段。在测试阶段,对 Swift 代码的分析以及相关的文档可能不如其他语言那样全面。
关于本文¶
本文介绍了数据流分析在 CodeQL 的 Swift 库中的实现方式,并包含一些示例帮助您编写自己的数据流查询。以下部分介绍了如何使用库进行本地数据流、全局数据流和污点跟踪。有关数据流建模的更一般介绍,请参阅“关于数据流分析”。
本地数据流¶
本地数据流跟踪数据在单个函数中的流动情况。本地数据流比全局数据流更简单、更快,也更精确。在查看更复杂的数据流跟踪之前,您应该始终考虑本地数据流跟踪,因为它对于许多查询来说已经足够了。
使用本地数据流¶
您可以通过导入 DataFlow
模块来使用本地数据流库。该库使用类 Node
来表示任何数据流经的元素。Node
类包含许多有用的子类,例如 ExprNode
用于表达式,而 ParameterNode
用于参数。您可以使用成员谓词 asExpr
和 getCfgNode
在数据流节点和表达式/控制流节点之间进行映射。
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() { ... }
...
}
您可以使用谓词 exprNode
和 parameterNode
从表达式和参数映射到它们的数据流节点。
/**
* 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 文档中。