CodeQL基础

安装

  1. CodeQL CLI引擎安装,https://github.com/github/codeql-cli-binaries/releases
1
2
3
4
5
6
# 下载
wget https://github.com/github/codeql-cli-binaries/releases/download/codeql-osx64.zip

# 将codeql添加至path中
echo "alias codeql=\"/Users/alphag0/Desktop/CodeQL/codeql/codeql\"" >> ~/.zshrc
source ~/.zshrc
  1. CodeQL SDK安装,https://github.com/github/codeql
  2. VSCode CodeQL扩展安装,扩展中搜索CodeQL进行安装,并配置CLI地址

数据库

  1. 准备源代码,对于闭源代码可以通过反编译的方式来获取(例如Java项目),本文基于项目micro_service_seclab进行学习
  2. 利用CodeQL CLI创建数据库
1
2
3
4
5
# 基本命令
codeql database create <数据库名> --language=<语言标识符> --source-root=<源码路径>

# 编译数据库
codeql database create ./databases/micro_service_seclab --language="java" --command="mvn clean compile -DskipTests" --source-root=./micro_service_seclab/ --overwrite
  1. 创建完成后在VSCode插件中加载编译后的数据库,选择Language为Java,安装依赖

基本用法

CodeQL包含用于查找每种受支持语言中最相关、最有趣的问题的查询功能,还可以编写自定义查询来查找与项目相关的特定问题,重要的查询类型包括:

  1. 警报查询,用于突出显示代码中特定位置问题的查询
  2. 路径查询,描述代码中source和sink之间信息流的查询

CodeQL报警查询基本结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
*
* Query metadata
*
*/

import /* ... CodeQL libraries or modules ... */

/* ... Optional, define CodeQL classes and predicates ... */

from /* ... variable declarations ... */
where /* ... logical formula ... */
select /* ... expressions ... */

CodeQL路径查询基本结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* ...
* @kind path-problem
* ...
*/

import <language>
// For some languages (Java/C++/Python/Rust/Swift) you need to explicitly import the data flow library, such as
// import semmle.code.java.dataflow.DataFlow or import codeql.swift.dataflow.DataFlow
...

module Flow = DataFlow::Global<MyConfiguration>;
import Flow::PathGraph

from Flow::PathNode source, Flow::PathNode sink
where Flow::flowPath(source, sink)
select sink.getNode(), source, sink, "<message>"

CodeQL查询文件(.ql)元数据属性:

属性 值类型 描述
@description <text> 用一句话或一段话描述查询的目的以及结果的_用途_或重要性
@id <text> 由小写字母或数字组成的单词序列,以逗号/或空格分隔-,用于标识和分类查询
@previous-id <text> 表示该查询结果之前已在其他查询中报告过
@kind problem
path-problem
标识查询是警报查询还是路径查询
@name <text> 用于定义查询标签的语句
@tags correctness
maintainability
readability
security
这些标签将查询归类到不同的类别中,以便于搜索和识别
@precision low
medium
high
very-high
表示查询结果中真正阳性结果(而非假阳性结果)的百分比
@problem.severity error
warning
recommendation
定义非安全查询生成的任何警报的严重级别
@security-severity <score> 定义查询的严重级别,介于0.010.0之间

有关CodeQL中包含的谓词、模块和类的详细信息,https://codeql.github.com/codeql-standard-libraries/

在CodeQL中存在两种数据流:

  1. 本地数据流:本地数据流是指单个方法或可调用函数内的数据流,本地数据流通常比全局数据流更简单、更快速、更精确,并且足以应对许多查询
  2. 全局数据流:全局数据流跟踪整个程序的数据流,然而,全局数据流的精确度低于局部数据流,并且分析通常需要更多的时间和内存

针对项目的部分漏洞学习全局数据流,查询语句如下:

  1. XXE漏洞查询语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* This is an automatically generated file
* @name xxe
* @kind path-problem
* @problem.severity warning
* @id java/micro-service-seclab/xxe
*/

import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources

/* 全局数据流 */
module XXEConfig implements DataFlow::ConfigSig {
/* 任何远程输入参数 */
predicate isSource(DataFlow::Node src) {
src instanceof RemoteFlowSource
}

/* parse方法的第0个实参 */
predicate isSink(DataFlow::Node snk) {
exists(MethodCall call |
call.getMethod().hasName("parse") and
snk.asExpr() = call.getArgument(0)
)
}

/* 可在此处补充 isBarrier / isAdditionalFlowStep 等高级规则 */
}

/* 实例化全局数据流 */
module XXEFlow = TaintTracking::Global<XXEConfig>;

/* 导入 PathGraph, 使用 flowPath 路径查询 */
import XXEFlow::PathGraph

/* 路径查询, source -> sink */
from XXEFlow::PathNode src, XXEFlow::PathNode snk
where XXEFlow::flowPath(src, snk)
select src.getNode(), src, snk, "远程输入数据可能流入 parse 方法, 存在 XXE 等风险"

  1. Fastjson漏洞查询语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* This is an automatically generated file
* @name fastjson
* @kind path-problem
* @problem.severity warning
* @id java/micro-service-seclab/fastjson
*/

import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources

module FastjsonConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) {
src instanceof RemoteFlowSource
}

predicate isSink(DataFlow::Node snk) {
exists(MethodCall call |
call.getMethod().hasName("parseObject") and
snk.asExpr() = call.getArgument(0)
)
}
}

module FastjsonFlow = TaintTracking::Global<FastjsonConfig>;
import FastjsonFlow::PathGraph

from FastjsonFlow::PathNode src, FastjsonFlow::PathNode snk
where FastjsonFlow::flowPath(src, snk)
select src.getNode(), src, snk, "远程输入数据可能流入 parseObject 方法, 存在 Fastjson 反序列化等风险"

  1. SQL漏洞查询语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* This is an automatically generated file
* @name sql
* @kind path-problem
* @problem.severity warning
* @id java/micro-service-seclab/sql
*/

import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources

module SQLConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) {
src instanceof RemoteFlowSource
}

predicate isSink(DataFlow::Node snk) {
exists(MethodCall call |
call.getMethod().hasName("query") and
snk.asExpr() = call.getArgument(0)
)
}

predicate isBarrier(DataFlow::Node sanitizer) {
sanitizer.getType() instanceof NumberType
or exists(ParameterizedType pt |
sanitizer.getType() = pt and
pt.getTypeArgument(0) instanceof NumberType
)
}
}

module SQLFlow = TaintTracking::Global<SQLConfig>;
import SQLFlow::PathGraph

from SQLFlow::PathNode src, SQLFlow::PathNode snk
where SQLFlow::flowPath(src, snk)
select src.getNode(), src, snk, "远程输入数据可能流入 query 方法, 存在 SQL 注入等风险"

  1. RCE漏洞查询语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* This is an automatically generated file
* @name rce
* @kind path-problem
* @problem.severity warning
* @id java/micro-service-seclab/rce
*/

import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.dataflow.TaintTracking
import semmle.code.java.dataflow.FlowSources

module RCEConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) {
src instanceof RemoteFlowSource
}

predicate isSink(DataFlow::Node snk) {
snk.asExpr() instanceof ArgumentToExec
}
}

module RCEFlow = TaintTracking::Global<RCEConfig>;
import RCEFlow::PathGraph

from RCEFlow::PathNode src, RCEFlow::PathNode snk
where RCEFlow::flowPath(src, snk)
select src.getNode(), src, snk, "远程输入数据可能流入 exec 方法, 存在 RCE 风险"

语法片段

  1. 获取setter方法
1
2
3
4
5
6
7
8
/** 返回 void、恰好 1 参,且名字以 set 开头 */
class SetterMethod extends Method {
SetterMethod() {
this.getReturnType() instanceof VoidType and
this.getNumberOfParameters() = 1 and
this.getName().matches("set%")
}
}
  1. 获取getter方法
1
2
3
4
5
6
7
8
9
10
11
/** 无参、非 void 返回,且名字以 get / is 开头 */
class GetterMethod extends Method {
GetterMethod() {
this.getNumberOfParameters() = 0 and
not this.getReturnType() instanceof VoidType and
(
this.getName().matches("get%") or
this.getName().matches("is%")
)
}
}
  1. 获取无参构造方法
1
2
3
4
5
6
/** 获取所有无参构造器 */
class NoArgConstructor extends Constructor {
NoArgConstructor() {
this.getNumberOfParameters() = 0
}
}
  1. 获取toString/hashCode/equals方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ToStringMethod extends Method {
ToStringMethod() {
this.hasName("toString") and
this.getNumberOfParameters() = 0 and
this.getReturnType().hasQualifiedName("java.lang", "String")
}
}

class HashCodeMethod extends Method {
HashCodeMethod() {
this.hasName("hashCode") and
this.getNumberOfParameters() = 0 and
this.getReturnType().hasQualifiedName("int")
}
}

class EqualsMethod extends Method {
EqualsMethod() {
this.hasName("equals") and
this.getNumberOfParameters() = 1 and
this.getParameter(0).getType() instanceof TypeObject and
this.getReturnType().hasQualifiedName("boolean")
}
}

参考

CodeQL Documentation

CodeQL library for Java/Kotlin