简单来说,CodeQL就是一个静态分析(SAST)工具,可以在白盒场景通过编写QL制定的规则,自动化的扫描代码。
CodeQL分引擎和SDK两部分,引擎部分不开源,主要负责解析规则。SDK是开源的,包含很多漏洞规则,也可以自己写漏洞规则进行使用。
引擎部分需要配置一下环境变量

SDK部分直接拉源代码就可以了
接下来拉一个项目,尝试一下CodeQL
这里我拉了这个Java靶场进行测试,拉下来后需要配一下数据库,确保项目可以正常运行。
接下来安装vscode的codeql插件,配置codeql所在的目录

在java/ql/examples 目录下创建demo.ql,内容为select "Hello World
,并且右键选择CodeQL: Run Query

到这里环境搭建的工作就完成了。
写起来比较简略,实际上还是踩了不少坑的,不过毕竟用Linux物理机,平常遇到奇奇怪怪的问题太多了,用搜索引擎结合一些文章就解决了
ql的语法和sql的语法有一些相似的地方
由于CodeQL实际的查询是对AST的查询,因此QL的类库是与AST对应的。
可以看一下AST的样子

from [datatype] var
where condition(var = something)
select varexample:
import java // 引入CodeQL类库
from int i // 表示所有int类型的数据
where i = 1 // 表示条件:当i等于1时
select i // 输出i
Method:方法类,Method method表示获取当前项目中所有的方法
MethodAccess:方法调用类,MethodAccess call表示获取当前项目当中的所有方法调用
Parameter:参数类,Parameter表示获取当前项目当中所有的参数
example:
import java
from Method method // 表示所有方法
where method.hasName("getStudent") // 表示条件:方法名为getStudent
select method.getName(), method.getDeclaringType() // 输出方法的名称,和方法所属的类名
当where部分过长时,可以用谓词这个语法,把很长的查询语句封装成函数。
predicate 关键词用于声明谓词
exists 子查询,它根据内部的子查询返回true or false,来决定筛选出哪些数据。
利用上面这两个语法,我们可以把判断方法名称是否为getStudent的where部分,封装成函数。这个函数就被称为谓词。
import java
predicate isStudent(Method method){
exists(|method.hasName("getStudent"))
} // |操作符返回查询结果的数量,大于0则true
from Method method
where isStudent(method)
select method.getName(), method.getDeclaringType().getName()SAST的理念中通常会提到这个三元组(source,sink,sanitizer)
source是指漏洞污染链条的输入点。比如获取http请求的参数部分,就是非常明显的Source。
sink是指漏洞污染链条的执行点,比如SQL注入漏洞,最终执行SQL语句的函数就是sink(这个函数可能叫query或者exeSql,或者其它)。
sanitizer又叫净化函数,是指在整个的漏洞链条当中,如果存在一个方法阻断了整个传递链,那么这个方法就叫sanitizer。
sourcesource,在我们这个java靶场中,具体来看就是后端接口的参数
@RequestMapping(value = "/one")
public List<Student> one(@RequestParam(value = "username") String username) {
return indexLogic.getStudent(username);
}例如这段代码,source就是username参数
对于source的检测,我们可以用CodeQL的SDK提供的检测规则
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }其中,DataFlow::Node表示一个数据流节点,可以是数据流源、汇或传输节点。RemoteFlowSource是一个表示远程数据流源的CodeQL类。
通俗的说,这里定义了一个叫isSource的谓词,来判断传入的这个节点是不是远程数据流源(RemoteFlowSource)。
sink这里我们以找sql注入的漏洞为例,sink就应该是qurey方法
override predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodAccess call |
method.hasName("query")
and
call.getMethod() = method and
sink.asExpr() = call.getArgument(0)
)
}这里我们声明谓词isSink,通过exists来判断名为query的方法,并且设置第一个参数为sink
从Source向Sink的数据流是否能够走通决定了是否有可能存在漏洞,可以用CodeQL的语法config.hasFlowPath(source, sink)来判断。
前面分别讲了Source、Sink、Data Flow的定义方法,还需要一些语法将他们串起来,才是一个完成的demo。
source和sink的定义使用到的方法,需要继承自TaintTracking::Configuration类。
完整Demo:
/**
* @id java/examples/demo
* @name Sql-Injection
* @description Sql-Injection
* @kind path-problem
* @problem.severity warning
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.QueryInjection
import DataFlow::PathGraph
class VulConfig extends TaintTracking::Configuration {
VulConfig() { this = "SqlInjectionConfig" }
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodAccess call |
method.hasName("query")
and
call.getMethod() = method and
sink.asExpr() = call.getArgument(0)
)
}
}
from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"
非常的有意思,不过这里仍然存在问题,就是误报问题。

这里也就是前面提到的三元组中,sanitizer的问题。
CodeQL默认规则中,没有对List<Long>这样的复合类型做判断,因此需要手动写一个isSantizer的谓词做判断,来解决误报的问题。
这段的意思是,如果当前node节点为基础类型、数字类型、泛型数字类型时,就切断数据流。
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or
node.getType() instanceof BoxedType or
node.getType() instanceof NumberType or
exists(ParameterizedType pt| node.getType() = pt and pt.getTypeArgument(0) instanceof NumberType )
}对于靶场中这段代码,没有捕获到。
public List<Student> getStudentWithOptional(Optional<String> username) {
String sqlWithOptional = "select * from students where username like '%" + username.get() + "%'";
//String sql = "select * from students where username like ?";
return jdbcTemplate.query(sqlWithOptional, ROW_MAPPER);
}这里是因为CodeQL的默认规则,没有对Optional这种类型做判断,这里可以选择手动添加对于Optional这种类型的检测。
predicate isTaintedString(Expr expSrc, Expr expDest) {
exists(Method method, MethodAccess call, MethodAccess call1 | expSrc = call1.getArgument(0) and expDest=call and call.getMethod() = method and method.hasName("get") and method.getDeclaringType().toString() = "Optional<String>" and call1.getArgument(0).getType().toString() = "Optional<String>" )
}
文章主要是本人的学习笔记,内容大多数都是对其他文章的参考,还是建议看一手文章进行学习