4

我想使用通过用户输入污染其参数的函数进行 TaintTracking。例子:

fgets(buf, sizeof(buf), stdin); // buf is tainted
[...]
n = strlen(buf); // tainted argument to strlen
[...]
memcpy(somewhere, buf, n) // tainted call to memcpy

Semmle 应该能够通过如下所示的查询来发现这一点(仅以 fgets->strlen 为例)。我从 SecurityOptions 借用代码:

import cpp
import semmle.code.cpp.dataflow.TaintTracking

class IsTaintedArg extends string {
  IsTaintedArg() { this = "IsTaintedArg" }
  predicate userInputArgument(FunctionCall functionCall, int arg) {
    exists(string fname |
      functionCall.getTarget().hasGlobalName(fname) and
      exists(functionCall.getArgument(arg)) and (fname = "fgets" and arg = 0) // argument 0 of fgets is tainted
    )
  }

  predicate isUserInput(Expr expr, string cause) {
    exists(FunctionCall fc, int i |
      this.userInputArgument(fc, i) and
      expr = fc.getArgument(i) and
      cause = fc.getTarget().getName()
    )
  }
}

class TaintedFormatConfig extends TaintTracking::Configuration {
  TaintedFormatConfig() { this = "TaintedFormatConfig" }
  override predicate isSource(DataFlow::Node source) {
    exists (IsTaintedArg opts |
      opts.isUserInput(source.asExpr(), _)
    )
  }
  override predicate isSink(DataFlow::Node sink) { 
    exists (FunctionCall fc | sink.asExpr() = fc.getArgument(0) and fc.getTarget().hasName("strlen")) // give me all calls that land in strlen's first argument
  }
}

from TaintedFormatConfig cfg, DataFlow::Node source, DataFlow::Node sink
where cfg.hasFlow(source, sink)
select sink, source

然而,它看起来不像在工作。

当我只是查询cfg.isSource()cfg.isSink(),源和接收器都被识别。但hasFlow()仍然没有返回任何内容 - 尽管路径绝对应该存在。

  • 我正在使用 libssh2 来测试我的发现,示例流程存在于此处

  • 我要测试的查询在这里

有谁知道我在上面的查询中可能做错了什么?

4

1 回答 1

4

丢失的位在 中isSource,污点跟踪从第 0 个参数开始到fgets。使用asExpr将描述从该参数 fgets. 我们想要的是通过该论点流出fgets。您可以通过替换asExprasDefiningArgument.

是您的查询结果的链接,我同时使用了asExprasDefiningArgumentin isSource。这意味着如果您isUserInput将来扩展,它的表达式将被视为源,既作为它们的值,又作为输出参数。

新版本的查询有 8 个结果,其中一些结果在您只看到源和接收器时很难理解,因为它们可以在不同的文件中。我已经制作了查询的清理版本

  • 生成源和汇之间的路径解释 ( @kind path-problem),
  • 删除IsTaintedArg辅助谓词的类包装,
  • 删除一些未使用的参数和检查,以及
  • 将调用添加到asDefiningArgument.

这是完整的查询:

/**
 * @kind path-problem
 * @id taint-to-strlen
 */
import cpp
import semmle.code.cpp.dataflow.DataFlow
import DataFlow::PathGraph

predicate userInputArgument(FunctionCall functionCall, int arg) {
  functionCall.getTarget().hasGlobalName("fgets") and
  arg = 0 // argument 0 of fgets is tainted
}

predicate isUserInput(Expr expr) {
  exists(FunctionCall fc, int i |
    userInputArgument(fc, i) and
    expr = fc.getArgument(i)
  )
}

class TaintedFormatConfig extends DataFlow::Configuration {
  TaintedFormatConfig() { this = "TaintedFormatConfig" }
  override predicate isSource(DataFlow::Node source) {
    isUserInput(source.asExpr())
    or
    isUserInput(source.asDefiningArgument())
  }
  override predicate isSink(DataFlow::Node sink) { 
    exists (FunctionCall fc | sink.asExpr() = fc.getArgument(0) and fc.getTarget().hasName("strlen"))
  }
}

from TaintedFormatConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink, source, sink, "Taint from fgets call in " + source.getNode().getFunction().getFile().getBaseName()

您可以在https://lgtm.com/query/4800800615370766111/查看结果。

于 2019-09-30T09:45:02.970 回答