入门
首先给出我学习的网站
https://www.freebuf.com/articles/web/283795.html
https://github.com/l4yn3/micro_service_seclab/
安装
引擎安装
我的安装位置是C:\Users\Yang_99\Desktop\CodeQL\
https://github.com/github/codeql-cli-binaries/releases
然后我们可以把下载好的这个文件夹添加为环境变量
然后命令行检测一下
SDK安装
我们下载SDK,同样放入CodeQL文件夹
vscode插件安装
直接安装codeql插件。然后设置好
测试”hello world”
生成Database
为了测试我们刚才的开发环境是否可以正常调试,我们实现一个简单的”Hello World”。
由于CodeQL
的处理对象并不是源码本身,而是中间生成的AST结构数据库,所以我们先需要把我们的项目源码转换成CodeQL
能够识别的CodeDatabase
。
我们使用如下命令进行CodeDatabase
的生成工作。
codeql database create micro-service-seclab-database1 --language="java" --command="mvn clean install -Dmaven.test.skip=true" --source-root=C:\Users\Yang_99\Desktop\CodeQL\micro_service_seclab-main
我们来解释一下上面生成database命令的语句:
codeql database create ~/CodeQL/databases/codeql_demo 当然是指我们要创建的database为~/CodeQL/databases/codeql_demo(注意:要先创建~/CodeQL/databases/目录)。
--language="java" 表示当前程序语言为Java。
--command="mvn clean install --file pom.xml" 编译命令(因为Java是编译语言,所以需要使用–command命令先对项目进行编译,再进行转换,python和php这样的脚本语言不需要此命令)
--source-root=~/CodeQL/micro-service-seclab/ 这个当然指的是项目路径
我们执行以上命令后,首先会对项目进行编译,然后就会提示创建数据库成功,访问C:\Users\Yang_99\Desktop\CodeQL\test\micro-service-seclab-database
即可
这就是一个codeql数据库。我们要对它进行分析。
导入Database
和SQL语言一样,我们执行QL查询,肯定是要先指定一个数据库才可以。
编写Helloworld
我们打开刚才下载的SDK目录(用vscode打开)
这个表示输出Hello world
在这个目录下新建demo.ql 然后右键运行,那么就成功搭建了codeql。
QL语法
CodeQL的查询语法有点像SQL,如果你学过基本的SQL语句,基本模式应该不会陌生。
第一行表示我们要引入CodeQl的类库,因为我们分析的项目是java的,所以在ql语句里,必不可少。
from int i
,表示我们定义一个变量i,它的类型是int,表示我们获取所有的int类型的数据;
where i = 1
, 表示当i等于1的时候,符合条件;
select i
,表示输出
QL查询的语法结构为:
from [datatype] var
where condition(var = something)
select var
类库
上面我们提到,我们需要把我们的靶场项目,使用CodeQL引擎转换成CodeQL可以识别的database(micro-service-seclab-database),这个过程当中,CodeQL引擎把我们的java代码转换成了可识别的AST数据库。
要先选择为当前数据库,然后生成AST
AST Code大体长这个样子:
我们的类库实际上就是上面AST的对应关系。
怎么理解呢?比如说我们想获得所有的类当中的方法,在AST里面Method代表的就是类当中的方法;比如说我们想过的所有的方法调用,MethodAccess获取的就是所有的方法调用。
我们经常会用到的ql类库大体如下:
名称 | 解释 |
---|---|
Method | 方法类,Method method表示获取当前项目中所有的方法 |
MethodAccess | 方法调用类,MethodAccess call表示获取当前项目当中的所有方法调用 |
Parameter | 参数类,Parameter表示获取当前项目当中所有的参数 |
结合ql的语法,我们尝试获取micro-service-seclab项目当中定义的所有方法:
import java
from Method method
select method
我们再通过Method类内置的一些方法,把结果过滤一下。比如我们获取名字为getStudent的方法名称。
import java
from Method method
where method.hasName("getStudent")
select method.getName(), method.getDeclaringType()
method.getName() 获取的是当前方法的名称
method.getDeclaringType() 获取的是当前方法所属class的名称。
谓词
和SQL一样,where部分的查询条件如果过长,会显得很乱。CodeQL提供一种机制可以让你把很长的查询语句封装成函数。
这个函数,就叫谓词。
语法解释
predicate 表示当前方法没有返回值。
exists子查询,是CodeQL谓词语法里非常常见的语法结构,它根据内部的子查询返回true or false,来决定筛选出哪些数据。
设置Source和Sink
什么是source和sink
在代码自动化安全审计的理论当中,有一个最核心的三元组概念,就是(source,sink和sanitizer)。
source是指漏洞污染链条的输入点。比如获取http请求的参数部分,就是非常明显的Source。
sink是指漏洞污染链条的执行点,比如SQL注入漏洞,最终执行SQL语句的函数就是sink(这个函数可能叫query或者exeSql,或者其它)。
sanitizer又叫净化函数,是指在整个的漏洞链条当中,如果存在一个方法阻断了整个传递链,那么这个方法就叫sanitizer。
只有当source和sink同时存在,并且从source到sink的链路是通的,才表示当前漏洞是存在的。
设置Source
在CodeQL中我们通过
override predicate isSource(DataFlow::Node src) {}
方法来设置source
。
思考一下,在我们的靶场系统(micro-service-seclab)中,source是什么?
我们使用的是Spring Boot
框架,那么source就是http参数入口的代码参数,在下面的代码中,source就是username:
@RequestMapping(value = "/one")
public List<Student> one(@RequestParam(value = "username") String username) {
return indexLogic.getStudent(username);
}
在下面的代码中,source就是Student user
(user为Student类型,这个不受影响)。
@PostMapping(value = "/object")
public List<Student> objectParam(@RequestBody Student user) {
return indexLogic.getStudent(user.getUsername());
}
本例中我们设置Source的代码为:
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
设置Sink
在CodeQL中我们通过
override predicate isSink(DataFlow::Node sink) {
}
方法设置Sink。
在本案例中,我们的sink应该为query
方法(Method)的调用(MethodAccess),所以我们设置Sink为:
override predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodAccess call |
method.hasName("query")
and
call.getMethod() = method and
sink.asExpr() = call.getArgument(0)
)
}
注:以上代码使用了exists子查询语法,格式为exists(Obj obj| somthing), 上面查询的意思为:查找一个query()方法的调用点,并把它的第一个参数设置为sink。
在靶场系统(micro-service-seclab
)中,sink就是:
jdbcTemplate.query(sql, ROW_MAPPER);
因为我们测试的注入漏洞,当source变量流入这个方法的时候,才会发生注入漏洞!
Flow数据流
设置好Source和Sink,就相当于搞定了首尾,但是首尾是否能够连通才能决定是否存在漏洞!
一个受污染的变量,能够毫无阻拦的流转到危险函数,就表示存在漏洞!
这个连通工作就是CodeQL引擎本身来完成的。我们通过使用config.hasFlowPath(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,系统就会自动帮我们判断是否存在漏洞了。
初步成果
在CodeQL中,我们使用官方提供的TaintTracking::Configuration方法定义source和sink,至于中间是否是通的,这个后面使用CodeQL提供的config.hasFlowPath(source, sink)
来帮我们处理。
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)
)
}
}
CodeQL语法和Java类似,extends代表集成父类TaintTracking::Configuration。
这个类是官方提供用来做数据流分析的通用类,提供很多数据流分析相关的方法,比如isSource(定义source),isSink(定义sink)
src instanceof RemoteFlowSource 表示src 必须是 RemoteFlowSource类型。在RemoteFlowSource里,官方提供很非常全的source定义,我们本次用到的Springboot的Source就已经涵盖了。