CodeQL学习笔记(一)

CodeQL简介

CodeQL是一种用于查找潜在漏洞和安全问题的查询语言。它可以帮助开发人员更轻松地检查代码,以确保它们的应用程序没有安全漏洞。CodeQL可以检查源代码,以及编译后的代码,以及可执行文件,以及其他形式的代码。它还可以检查源代码以外的元数据,例如文档和注释。

CodeQL历史

该项目的历史为:Semmle公司最早独创性的开创了一种QL语言,Semmle QL,并且运行在自家LGTM平台上。

LGTM平台上存放的就是一些开源项目,用户可以选择分析的语言,编写ql语句进行程序安全性查询。

2019年,GitHub为了解决其托管的海量项目的安全性问题,收购了Semmle公司,并宣布开源CodeQL的部分规则,这样全世界的安全工程师就可以贡献高效的QL审计规则给github,帮助它解决托管项目的安全问题,而对于安全人员也多了一个非商业的开源代码自动化审计工具。

环境配置

CodeQL本身包含两部分:解析引擎和SDK

安装解析引擎

解析引擎用来解析我们编写的规则,虽然不开源,但是我们可以直接在官网下载二进制文件直接使用。

https://github.com/github/codeql-cli-binaries/releases/download/v2.12.2/codeql-win64.zip

将codeql.exe添加到环境变量PATH中

image-20230228200459251

安装SDK

SDK完全开源,里面包含大部分现成的漏洞规则,我们也可以利用其编写自定义规则。

github/codeql: CodeQL: the libraries and queries that power security researchers around the world, as well as code scanning in GitHub Advanced Security

解压,然后重命名为ql

image-20230228201156044

安装插件

如下,windows的Executable path要指定到codeql.exe

image-20230228200855092

AST

抽象语法树(Abstract Syntax Tree,简称 AST

抽象语法树(abstract syntax tree,AST) 是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并不会表示出真实语法出现的每一个细节,比如说,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现。 抽象语法树并不依赖于源语言的语法,也就是说语法分析阶段所采用的上下文无文文法,因为在写文法时,经常会对文法进行等价的转换(消除左递归,回溯,二义性等),这样会给文法分析引入一些多余的成分,对后续阶段造成不利影响,甚至会使合个阶段变得混乱。因些,很多编译器经常要独立地构造语法分析树,为前端,后端建立一个清晰的接口。 抽象语法树在很多领域有广泛的应用,比如浏览器,智能编辑器,编译器。

生成AST数据库

运行CodeQL之前,需要将源码生成对应AST结构数据库

对于java这种需要编译的语言,加上--command="mvn clean install --file pom.xml",在pom.xml所在目录执行如下命令

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

其中

  1. codeql database create xxx表示生成xxx数据库
  2. --language=java 表示指定程序语言为java
  3. --command="mvn clean install --file pom.xml"表示编译(python和php无需执行)
  4. --source-root=path指定项目路径(此处未使用)、

如下,生成了对应的AST数据库

image-20230228202216515

在CodeQL插件中添加数据库

image-20230228203634327

如下,添加成功

image-20230228203903832

编写简单的hello world

vscode打开之前的ql文件夹(SDK),创建demo.ql,路径java\ql\examples\demo.ql

image-20230228201319719

测试经典的hello world

1
select "hello world"

如下

image-20230228202411514

语法

名称 解释
Method 方法类,Method method表示获取当前项目中所有的方法
MethodAccess 方法调用类,MethodAccess call表示获取当前项目当中的所有方法调用
Parameter 参数类,Parameter表示获取当前项目当中所有的参数
method.getName() 获取的是当前方法的名称
method.getDeclaringType() 获取的是当前方法所属class的名称

查找所有的方法

1
2
3
4
import java

from Method method
select method

谓词

个人理解为函数,比如要找方法commonHttpClient所在的类

image-20230301193808702

1
2
3
4
5
6
7
8
9
import java

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

from Method method
where isStudent(method)
select method.getName(), method.getDeclaringType()// 返回方法以及方法所在类

运行结果

image-20230301193757364

其中

predicate 表示当前方法没有返回值。

exists子查询,它根据内部的子查询返回true or false,从而删选数据

污点分析

污点分析可以抽象成一个三元组<sources, sinks, sanitizers>的形式,

sources:污点源,代表直接引入不受信任的数据或者机密数据到系统中
sink:污点汇聚点,代表直接产生安全敏感操作或者泄露隐私数据到外界
sanitizer:无害处理,代表通过数据加密或者移除危害操作等手段使数据传播不再对软件系统的信息安全产生危害

只有当source和sink同时存在,并且从source到sink的链路是通的,才表示当前漏洞是存在的。

设置source

instance作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型

src instanceof RemoteFlowSource 表示src 必须是 RemoteFlowSource类型。RemoteFlowSource是在SDK中已经定义好的,包含了大多数常用的source节点。

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

image-20230301204107573

尝试对rce漏洞进行测试

image-20230301205214748

设置sink

设置方法

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

可以将sink设置为eval函数的方法调用,代码如下

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
/**
* @id java/examples/vuldemo
* @name Rce
* @description Rce
* @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 = "RceConfig" }

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

override predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodAccess call |
method.hasName("eval") 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"

运行结果

image-20230301225535695

上面的ql代码,关键看exists子查询,如下

1
2
3
exists(Method method, MethodAccess call |
method.hasName("eval") and call.getMethod() = method and sink.asExpr() = call.getArgument(0)
)

意思是将方法访问的具体方法设置为eval,且将方法访问(也就是engine.eval(cmd, bindings))的第一个参数(也就是cmd)作为sink点

具体的验证方法

1
2
3
4
5
6
7
8
9
10
import java 
predicate isEval(MethodAccess call ) {
exists(Method method |
method.hasName("eval") and call.getMethod() = method
)
}

from MethodAccess call
where isEval(call)
select call.getArgument(0),call.getArgument(1)// 将methodaccess的前两个参数进行打印

运行结果

image-20230301230709216

参考链接

  1. 膜大佬 CodeQL从入门到放弃 - FreeBuf网络安全行业门户