codeql学习小记-数据流

  1. 1. DataFlow模块
  2. 2. 局部数据流
    1. 2.1. 使用局部数据流
    2. 2.2. 局部污点跟踪
  3. 3. 全局数据流
    1. 3.1. 使用全局数据流
    2. 3.2. 使用全局污点跟踪

DataFlow模块

官方文档写了如何在java中用数据流分析

https://codeql.github.com/docs/codeql-language-guides/analyzing-data-flow-in-java/

这篇笔记算是简单翻译一下吧。

局部数据流

使用局部数据流

局部数据流库在模块DataFlow中。该模块定义了Node类,表示数据经过的任何元素。

Node又可以分为表达式节点(ExprNode)和参数节点(ParameterNode)

使用谓词asExper和asParameter在数据流节点和表达式节点/参数节点表示映射(或者使用谓词exprNode和parameterNode)

1
2
3
4
5
6
7
8
9
class Node {
/** Gets the expression corresponding to this node, if any. */
Expr asExpr() { ... }

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

...
}
1
2
3
4
5
6
7
8
9
/**
* Gets the node corresponding to expression `e`.
*/
ExprNode exprNode(Expr e) { ... }

/**
* Gets the node corresponding to the value of parameter `p` at function entry.
*/
ParameterNode parameterNode(Parameter p) { ... }

除此之外,还有个谓词localFlowStep(Node nodeFrom, Node nodeTo) 文档描述是

1648209015997.png

谓词localFlowStep(Node nodeFrom, Node nodeTo)保存着从节点nodeFrom到节点nodeTo的直接数据流。 可以使用+和*运算符递归的应用谓词,或者使用预定义的递归谓词localFlow,它等效于localFlowStep*

例如,可以直接找到从source到sink的数据流,有时甚至不需要多余的步骤

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

局部污点跟踪

例如,我们要查找FileReader的参数,官方文档给的是下面的规则

1
2
3
4
5
6
7
import java

from Constructor fileReader, Call call
where
fileReader.getDeclaringType().hasQualifiedName("java.io", "FileReader") and
call.getCallee() = fileReader
select call.getArgument(0)

做个小测试,测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.io.FileReader;

public class FileReaderTest {
public FileReaderTest() {
}

public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("/etc/passwd");

int i;
while((i = fr.read()) != -1) {
System.out.print((char)i);
}

}
}

1648207972784.png

成功查询到了FileReader的参数”/etc/passwd”

但这个规则只是从语句结构上查询,并不能做到污点跟踪。

1648209901700.png

局部污点追踪的库都保存在模块 TaintTracking 类似于局部数据流,有个差不多的谓词localTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo)

举个例子,如果我改成这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.FileReader;

public class FileReaderTest {
public static void main(String[] args) throws Exception
{
String fileName_1 = "/etc";
String fileName_2 = "/passwd";
String fileName = fileName_1 + fileName_2;
// pass the path to the file as a parameter
FileReader fr =
new FileReader(fileName);

int i;
while ((i=fr.read()) != -1)
System.out.print((char) i);
}

}

纯语法分析的话 结果是这样

1648210651514.png

文档给的规则:

1
2
3
4
5
6
7
8
9
import java
import semmle.code.java.dataflow.DataFlow

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

这个规则可以通过局部数据流找到所有跟fileName参数相关的表达式

1648210760615.png

之后可以把结果变得更具体

下面这个规则可以查找传递给FileReader的public参数

1
2
3
4
5
6
7
8
9
import java
import semmle.code.java.dataflow.DataFlow

from Constructor fileReader, Call call, Parameter p
where
fileReader.getDeclaringType().hasQualifiedName("java.io", "FileReader") and
call.getCallee() = fileReader and
DataFlow::localFlow(DataFlow::parameterNode(p), DataFlow::exprNode(call.getArgument(0)))
select p

下面规则可以查询对格式化字符串未进行硬编码的格式化函数的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.StringFormat

from StringFormatMethod format, MethodAccess call, Expr formatString
where
call.getMethod() = format and
call.getArgument(format.getFormatStringIndex()) = formatString and
not exists(DataFlow::Node source, DataFlow::Node sink |
DataFlow::localFlow(source, sink) and
source.asExpr() instanceof StringLiteral and
sink.asExpr() = formatString
)
select call, "Argument to String format method isn't hard-coded."

全局数据流

全局数据流是跟踪一整个项目的数据流,所以它比局部数据流更强大。但是也有缺点,全局数据流没有局部数据流精确,而且分析时需要耗费更多的内存。

使用全局数据流

通过继承DataFlow::Configuration类来使用全局数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
import semmle.code.java.dataflow.DataFlow

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

override predicate isSource(DataFlow::Node source) {
...
}

override predicate isSink(DataFlow::Node sink) {
...
}
}

configuration定义了下面几个谓词

  • isSource—defines where data may flow from
  • isSink—defines where data may flow to
  • isBarrier—optional, restricts the data flow
  • isAdditionalFlowStep—optional, adds additional flow steps

特征谓词MyDataFlowConfiguration()定义了configuration的名称,所以"MyDataFlowConfiguration"必须是独一无二的。

用谓词hasFlow(DataFlow::Node source, DataFlow::Node sink)分析数据流

1
2
3
from MyDataFlowConfiguration dataflow, DataFlow::Node source, DataFlow::Node sink
where dataflow.hasFlow(source, sink)
select source, "Data flow to $@.", sink, sink.toString()

使用全局污点跟踪

继承``TaintTracking::Configuration`进行全局污点跟踪

1
2
3
4
5
6
7
8
9
10
11
12
13
import semmle.code.java.dataflow.TaintTracking

class MyTaintTrackingConfiguration extends TaintTracking::Configuration {
MyTaintTrackingConfiguration() { this = "MyTaintTrackingConfiguration" }

override predicate isSource(DataFlow::Node source) {
...
}

override predicate isSink(DataFlow::Node sink) {
...
}
}

同样的 定义了下面谓词

  • isSource—defines where taint may flow from
  • isSink—defines where taint may flow to
  • isSanitizer—optional, restricts the taint flow
  • isAdditionalTaintStep—optional, adds additional taint steps

跟全局数据流相似,特征谓词 MyTaintTrackingConfiguration() 定义配置的唯一名称。

用谓词hasFlow(DataFlow::Node source, DataFlow::Node sink) 进行全局污点跟踪分析

还有一个class可以分析远程用户的source 这对发现安全问题很有帮助

1648215147304.png