本文最后编辑于 前,其中的内容可能需要更新。
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 { Expr asExpr () { ... } Parameter asParameter () { ... } ... }
1 2 3 4 5 6 7 8 9 ExprNode exprNode (Expr e) { ... }ParameterNode parameterNode (Parameter p) { ... }
除此之外,还有个谓词localFlowStep(Node nodeFrom, Node nodeTo)
文档描述是
谓词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 javafrom 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); } } }
成功查询到了FileReader的参数”/etc/passwd”
但这个规则只是从语句结构上查询,并不能做到污点跟踪。
局部污点追踪的库都保存在模块 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; FileReader fr = new FileReader(fileName); int i; while ((i=fr.read()) != -1 ) System.out.print((char ) i); } }
纯语法分析的话 结果是这样
文档给的规则:
1 2 3 4 5 6 7 8 9 import javaimport semmle.code.java.dataflow.DataFlowfrom 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参数相关的表达式
之后可以把结果变得更具体
下面这个规则可以查找传递给FileReader的public参数
1 2 3 4 5 6 7 8 9 import javaimport semmle.code.java.dataflow.DataFlowfrom 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.DataFlowclass 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.TaintTrackingclass 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 这对发现安全问题很有帮助