codeQLx学习(二)
0 条评论概述
之前文章入门了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)
找到了危险函数的第一个危险参数commands
我们很明显看出,危险参数由command传入,那么codeQL怎么识别呢?
这就要用到DataFlow库了。
引用方式为
import semmle.code.java.dataflow.DataFlow
使用方法为:
DataFlow::localFlow(DataFlow::parameterNode(source), DataFlow::exprNode(sink))
跟进可以看到
传入的参数为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
这样我们可以找到所有流入危险函数的值
危险参数是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
返回为空,说明在
commands.add(command);
数据流断了。