目录

Learn Codeql With L4yn3

看到了 Log4j2 漏洞是由 CodeQL 审计出来的之后,就很想把 CodeQL 学会,这篇文章是记录自己跟着《CodeQL从入门到放弃》一步一步操作的记录。

创建数据库

之前已经在网上搜集资料,但是发现执行以下代码的时候会报错

1
codeql database create micro-service-seclab-database --language="java"

起初我以为是我的 Macbook M1 未经适配产生的问题(因为到 Github 上查看 Issue 也有相同的问题),但是后来发现是由于没有加上 Java 的编译命令导致的,完整命令如下:

1
codeql database create micro-service-seclab-database --language="java" --command="mvn clean install --file pom.xml"

成功如下图:

https://cdn.bingbingzi.cn/blog/20211215195500.png
数据库创建成功

CodeQL语句

我使用的版本为 2.7.3,好像不能像文章中直接执行 ql 文件,需要先在 ql 文件同级目录下创建一个名为 qlpack.yml,内容如下:

1
2
3
name: mssql
version: 0.0.1
libraryPathDependencies: codeql/java-all

基本语法

QL查询的语法结构为:

1
2
3
from [datatype] var
where condition(var = something)
select var

经常会用到的 ql 类库如下:

名称 解释
Method 方法类,Method method表示获取当前项目中所有的方法
MethodAccess 方法调用类,MethodAccess call表示获取当前项目当中的所有方法调用
Parameter 参数类,Parameter表示获取当前项目当中所有的参数

Method

获取项目中定义的所有方法:

1
2
3
4
import java

from Method method
select method

运行结果如下:

https://cdn.bingbingzi.cn/blog/20211215200631.png
运行结果

获取名为 getStudent 的方法名称

1
2
3
4
5
import java

from Method method
where method.hasName("getStudent")
select method.getName(), method.getDeclaringType()

运行结果如下:

https://cdn.bingbingzi.cn/blog/20211215200909.png
运行结果

method.getName() 获取的是当前方法的名称

method.getDeclaringType() 获取的是当前方法所属class的名称。

谓词

个人理解谓词就是类似函数的东西,可以将很长的代码封装成函数,方便多次使用

对于上面的案例,可以写成如下,获得的结果跟上面是一样的:

1
2
3
4
5
6
7
8
9
import java

predicate isStudent(Method method) {
	exists(|method.hasName("getStudent"))
}

from Method method
where isStudent(method)
select method.getName(), method.getDeclaringType()

运行结果:

https://cdn.bingbingzi.cn/blog/20211215201511.png
运行结果

语法解释

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 中通过以下代码来设置 soure

1
override predicate isSource(DataFlow::Node src) {}

在本例中设置 Source 的代码为:

1
2
3
override predicate isSource(DataFlow::Node src) {
	src instanceof RemoteFlowSource
}

设置 Sink

在 CodeQL 中通过以下代码来设置 Sink

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

在本例中,Slik 的代码为:

1
2
3
4
5
6
7
8
9
override predicate isSink(DataFlow::Node sink) {
	exists(Method method, MethodAccess call|
		method.hasName("query")
		and
		call.getMethod() = method
		and
		sink.asExpr() = call.getArgument(0)
	)
}

Flow数据流

设置好 Source 和 Sink,就相当于搞定了首尾,但是首尾是否能够连通才能决定是否存在漏洞!

一个受污染的变量,能够毫无阻拦的流转到危险函数,就表示存在漏洞!

1
2
3
from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"

初步成果

第一版 demo.ql

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import java
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)
    )
  }
}