CodeQL 学习笔记【8】新版数据流节点查询

0 阅读4分钟

但我们查询数据流的时候,可能数据流会经过很多个节点,但是普通的查询结果一般只展示入口节点和出口节点,那中间节点是什么呢,万一跨越多个文件,不可能自己慢慢去看吧。

这个时候就需要使用 PathGraph 让 CodeQL 帮我们将中间的节点也都展示出来,方便我们进行查看。

更新 CodeQL

首先要做的是升级你的 CodeQL (如果你用的还是老版本的话 :),我直接升级到了最新版 CodeQL CLI v2.23.5

  1. 首先访问 github.com/github/code…

  2. 我是 MAC 所以我这里选择 codeql-osx64.zip ,这里按照自己的系统类型选择即可 image.png

  3. 然后将下载下来的 zip 解压缩,覆盖本来的 CodeQL 文件夹即可。

  4. 这里有个小插曲,当初接触 CodeQL 的时候,分不清 CodeQL CLICodeQL SDK,然后我安装的时候,文件夹是下面这样写的,我这里文件夹名字其实是错的,我用红色字体写出来的应该才是正确的文件夹。 image.png

  5. 好的,让我把这个历史遗留问题改了吧,改好后的结果如下: image.png

  6. 然后第三步是将解压缩的 zip 文件里的内容,给覆盖到 CodeQL-CLI 里面即可。

可视化查询数据流

这个时候就需要我们显示指定元数据中的 @kind 了。这里需要注意要在元数据中写明 @kind path-problem,然后还要导入 import DataFlow::PathGraph ,最后 select 也要满足 4 个参数的条件。

select p1,p2,p3,p4
p1 是指数据流节点从 source 开始展示还是 sink 开始展示
p2,一般是 source 
p3, 一般是 sink
p4, 一般是文字提示信息
/**
 * @kind path-problem
 * @problem.severity error
 * @id wegoattest/remote-url-creation
 */

import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import DataFlow::PathGraph

class Configuration extends DataFlow::Configuration {
  Configuration() {
    this = "Configuration"
  }

  override predicate isSource(DataFlow::Node source) {
    source instanceof RemoteFlowSource
  }

  override predicate isSink(DataFlow::Node sink) {
    exists(Call call |
      sink.asExpr() = call.getArgument(0) and
      call.getCallee().(Constructor).getDeclaringType().hasQualifiedName("java.net", "URL")
    )
  }
}


from DataFlow::PathNode source, DataFlow::PathNode sink, Configuration config
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "execute sink called with untrusted data"

然后就可以执行代码获取响应。

image.png 这个时候,CodeQL 插件就会在右侧展示整个数据流的 Path,将所有的节点都展示出来了。

新版数据流可视化:使用模块替换类

当我运行上述代码的时候,我发现 VS Code 提示我,这个 DataFlow::PathGraph 以后可能会弃用了,那么新的查询数据流节点的代码是什么呢?结果花了好多时间才找到,真的是太难了。

/**
 * @kind path-problem
 * @problem.severity error
 * @id wegoattest/remote-url-creation
 */

import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking

module MyFlowConfiguration implements DataFlow::ConfigSig {
  predicate isSource(DataFlow::Node source) {
    source instanceof RemoteFlowSource
  }

  predicate isSink(DataFlow::Node sink) {
    exists(Call call |
      sink.asExpr() = call.getArgument(0) and
      call.getCallee().(Constructor).getDeclaringType().hasQualifiedName("java.net", "URL")
    )
  }
}

module MyFlow = TaintTracking::Global<MyFlowConfiguration>;
import MyFlow::PathGraph

from MyFlow::PathNode source, MyFlow::PathNode sink
where MyFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "execute sink called with untrusted data"

首先我们自己定义一个模块 MyFlowConfiguration 继承 DataFlow::ConfigSig ,然后定义模块 MyFlow = TaintTracking::Global<MyFlowConfiguration>; 来全局进行污点追踪。

其中还有一个比较重要的是这么一句 import MyFlow::PathGraph 这样才能让 CodeQL 给你展示数据流节点

image.png

这样我们就用新的方法查询到了 数据流的节点,并且没有提示我们某个方法在未来要弃用了,很完美,但是这个过程也很痛苦,毕竟英文资料是真的难查。

而且前一章里的净化函数和续接函数也发生了一些变化。

我们将上一章讲净化函数 isSanitizer 的代码改成新版的 isBarrier 净化函数的代码

import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking

module MyFlowConfiguration implements DataFlow::ConfigSig {
 predicate isSource(DataFlow::Node source) {
    exists(source.asParameter())
  }

 predicate isSink(DataFlow::Node sink) {
    exists(Method method, MethodCall call |
        // 假设 createSecretFileWithRandomContents() 就是危险函数
        method.hasName("createSecretFileWithRandomContents")
        and
        call.getMethod() = method and
        sink.asExpr() = call.getArgument(0)
      )
  }

//  predicate isBarrier(DataFlow::Node node) {
//     exists(Method method, MethodCall call |
//         // 假设经过 reset 函数后就不危险了
//         method.hasName("reset") and call.getMethod() = method and node.asExpr() = call.getAnArgument())
//   }
}

module MyFlow = TaintTracking::Global<MyFlowConfiguration>;
import MyFlow::PathGraph

from MyFlow::PathNode source, MyFlow::PathNode sink
where MyFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "This $@ is written to a log file.", source.getNode(),
  "potentially sensitive information"

先把净化函数注释运行一遍,然后讲注释取消再运行一遍,然后查看一下区别吧。

净化函数被注释掉了

净化函数被注释掉了

净化函数放开注释

净化函数放开注释

isSanitizer 、isSanitizerIn 、isBarrier、isBarrierIn 举例说明

一般情况下,isSanitizer、isSanitizerIn 使用场景是 明确的消毒/编码函数

isBarrier、isBarrierIn 使用场景是 加密、序列化等数据转换

// 假设现在有一段 java 代码如下:
// userInput → validateInput() → databaseQuery()
// 我们假设 isSource 就是 userInput
// 假设 isSink 就是 databseQuery()

override predicate isSanitizer、isSanitizerIn(DataFlow::Node node) { 
    exists(CallExpr call | call.getCalleeName() = "validateInput"
    and node.asExpr() = call ) 
}
override predicate isBarrier、isBarrierIn(DataFlow::Node node) { 
    exists(CallExpr call | call.getCalleeName() = "encryptData"
    and node.asExpr() = call ) 
}

下面是 AI 生成的,不一定是对的 :) image.png

数据流续接函数也是一样 isAdditionalTaintStep 换成了 isAdditionalFlowStep,就不具体展示了。

参考链接

xz.aliyun.com/news/10300

github.blog/changelog/2…