基础QL语法
CodeQL的查询语句比较像SQL
结构
import java // 导入使用的库
from int i /* ... 变量声明... */
where i = 100 /* ... 逻辑公式 ... */
select i /* ... 表达式 ... */
解析
import java
,导入使用的库,因为分析的项目是java的
from int i
,表示我们定义一个变量i,它的类型是int,表示获取所有的int类型的数据;
where i = 100
, 表示当i等于1的时候,符合条件;(这是=
是一个等于的意思 ==
,不是赋值)
select i
,就是输出 i
结果
常用类库
经常会用到的ql类库大体如下:
名称 | 解释 |
---|---|
Method | 方法类,Method method表示获取当前项目中所有的方法 |
MethodAccess | 方法调用类,MethodAccess call表示获取当前项目当中的所有方法调用 |
Parameter | 参数类,Parameter表示获取当前项目当中所有的参数 |
以查询所有方法为例
结构
import java
from Method method
select method
结果
这个项目被查询到的方法有46361个,显然这个结果太多了,那么接下来就要进行过滤的操作了
过滤
和sql语句一样,想要对查询结果进行过滤可以使用where语句
获取名字为 getValue的方法名称。
结构
import java
from Method method
where method.hasName("getValue")
select method.getName(), method.getDeclaringType()
结果
谓词
在进行过滤的时候显然不会是一段简单的语句,经常来说会是一段很长的语句,CodeQL提供一种机制可以让你把很长的查询语句封装成函数。
这个函数在CodeQL中交过谓词。
上面的过滤示例可以进行如下改写:
结构
import java
predicate hasgetValue(Method method) {
exists(|method.hasName("getValue"))
}
from Method method
where hasgetValue(method)
select method.getName(), method.getDeclaringType()
解析
predicate 表示当前方法没有返回值。
exists子查询,是CodeQL谓词语法里非常常见的语法结构,它根据内部的子查询返回true or false,来决定筛选出哪些数据。
结果
污点分析
使用CodeQL进行审计,污点分析肯定是要的
什么是source和sink
在代码自动化安全审计的理论当中,有一个最核心的三元组概念,就是(source,sink和sanitizer)。
source是指漏洞污染链条的输入点。比如获取http请求的参数部分,就是非常明显的Source。
sink是指漏洞污染链条的执行点,比如SQL注入漏洞,最终执行SQL语句的函数就是sink(这个函数可能叫query或者exeSql,或者其它)。
sanitizer又叫净化函数,是指在整个的漏洞链条当中,如果存在一个方法阻断了整个传递链,那么这个方法就叫sanitizer。
只有当source和sink同时存在,并且从source到sink的链路是通的,才表示当前漏洞是存在的。
设置Source
override predicate isSource(DataFlow::Node source) {
exists(....)
}
设置Sink
override predicate isSink(DataFlow::Node sink) {
exists(....)
}
Flow数据流
设置好Source和Sink后,如果一个受污染的变量,能够毫无阻拦的流转到危险函数,就表示存在漏洞。
from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"
我们传递给config.hasFlowPath(source, sink)
之前定义好的source和sink,系统就会判断是否存在漏洞了。
isAdditionalTaintStep方法
在有些情况下CodeQL在检测的时候会将两个节点认为是不连通的,这就需要我们人为设置他们是连通的
isAdditionalTaintStep方法是CodeQL的类TaintTracking::Configuration
提供的的方法
作用
将一个可控节点A强制传递给另外一个节点B,那么节点B也就成了可控节点。
原型
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
isTaintedString(node1.asExpr(), node2.asExpr())
}
}
误报解决(净化函数)
在分析的时候会发现有的调用链不存在漏洞
这就属于误报,那么我们就需要消除误报,消除的方法就是利用isSanitizer
函数,重写TaintTracking::Configuration
中的isSanitizer
函数,当流到达这个节点后中断
instanceof
和java类似,比如sink instanceof QueryInjectionSink
表示判断sink
是QueryInjectionSink
类型
我们要实现这种机制,只需要创建一个abstract抽象类
,如下图,不过这里和java的抽象类有区别,只要我们的子类继承了这个类,那么所有子类都会被调用
递归
CodeQL里面的递归调用语法是:在谓词方法的后面跟*
或者+
,来表示调用0次以上和1次以上(和正则类似),0次会打印自己。
类型过滤
CodeQL通过.(type)
进行类型过滤,可以理解成filter,它的意思是将前面的结果符合Type
的数据保留
如.(RefType)
,就是保留RefType
类的内容
lombok
开发过java的应该都清楚,由于java的封装特性,每一个变量都要写setter
和setter
很麻烦,所以就有了lombok
,引入以来后通过@Data
注解就可以自动实现getter
和setter
(不是自动补全代码的方式实现)
但是codeql不能识别lombok
的getter
和setter
,所以可能存在问题也发现不了,因此用codeql分析代码的时候,如果存在lombok
,那么可以通过如下的方法快速还原setter
和getter
方法,来自github issue
# get a copy of lombok.jar
wget https://projectlombok.org/downloads/lombok.jar -O "lombok.jar"
# run "delombok" on the source files and write the generated files to a folder named "delombok"
java -jar "lombok.jar" delombok -n --onlyChanged . -d "delombok"
# remove "generated by" comments
find "delombok" -name '*.java' -exec sed '/Generated by delombok/d' -i '{}' ';'
# remove any left-over import statements
find "delombok" -name '*.java' -exec sed '/import lombok/d' -i '{}' ';'
# copy delombok'd files over the original ones
cp -r "delombok/." "./"
# remove the "delombok" folder
rm -rf "delombok"