概述

之前文章入门了codeQL,这篇文章来学习codeQL的重点,数据流

数据流

什么是数据流呢,我们在代码审计中要先找到危险函数,如readfile等。readfile入参为param,如果一个外部输入参数如:$GET['file'],通过一些列赋值传递给了param,这之间的通道便被称为数据流。$GET['file']此类称为source,readfile此类危险函数称为sink。中间的通道被称为数据流flow,这之间还有净化函数Sanitizer,就是安全函数,在审计中比如黑白名单、类型强转等等。

codeQL中的数据流

本地数据流

本地数据流很好理解,就是在单个方法中的数据流。优点是快速准确。
在codeQl中为:

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

以胜哥的项目为案例:
在com.l4yn3.microserviceseclab.controller.RceController#One中

    @RequestMapping(value = "one")
    public StringBuffer One(@RequestParam(value = "command") String command) {
        StringBuffer sb = new StringBuffer();
        List<String> commands = new ArrayList<>();
        commands.add(command);

        ProcessBuilder processBuilder = new ProcessBuilder(commands);
        processBuilder.redirectErrorStream(true);
        try {
            Process process = processBuilder.start();

            BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            br.close();
        } catch (Exception e) {

        }
        return sb;
    }

很明显,危险函数是new ProcessBuilder(commands)

我们使用下面语法来找到这个调用

import java

from Constructor ProcessBuilder, Call call
where
ProcessBuilder.getDeclaringType().hasQualifiedName("java.lang", "ProcessBuilder") and
  call.getCallee() = ProcessBuilder
select call.getArgument(0)

image.png
找到了危险函数的第一个危险参数commands

我们很明显看出,危险参数由command传入,那么codeQL怎么识别呢?
这就要用到DataFlow库了。
引用方式为

import semmle.code.java.dataflow.DataFlow

使用方法为:

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

image.png
跟进可以看到
传入的参数为Node。
其结构如下:

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

  /** Gets the parameter corresponding to this node, if any. */
  Parameter asParameter() { ... }

  ...
}

分为表达式节点和参数节点
很容易理解,比如 x=y,那么x就为参数节点,而y就为表达式节点。

这样我们编写codeql代码如下:

import java
import semmle.code.java.dataflow.DataFlow

from Constructor ProcessBuilder, Call call ,Expr src
where
ProcessBuilder.getDeclaringType().hasQualifiedName("java.lang", "ProcessBuilder") and
  call.getCallee() = ProcessBuilder and
  DataFlow::localFlow(DataFlow::exprNode(src), DataFlow::exprNode(call.getArgument(0)))
select src

这样我们可以找到所有流入危险函数的值
image.png

危险参数是commond,为什么不行呢?
我们将source限制为Parameter,使用如下语法:

import java
import semmle.code.java.dataflow.DataFlow

from Constructor ProcessBuilder, Call call ,Parameter src
where
ProcessBuilder.getDeclaringType().hasQualifiedName("java.lang", "ProcessBuilder") and
  call.getCallee() = ProcessBuilder and
  DataFlow::localFlow(DataFlow::parameterNode(src), DataFlow::exprNode(call.getArgument(0)))
select src

image.png
返回为空,说明在

commands.add(command);

数据流断了。