Coql初始学习
CodeQL使用
CodeQL简述
CodeQL概述
CodeQL是一款静态漏洞检测工具,支持对C/C++,Python,Java,JS等语言的漏洞发现。CodeQL扫描的对象并非源码本身,它是通过搜索源代码相关信息以及数据之间的相互关系,将这些信息生成为AST结构的数据库(CodeDatabase)。然后利用CodeQL特有的数据库查询语句QL,对数据进行查询从而识别出源代码中的漏洞
环境准备
跟据概述我们大致需要准备以下环境
- 源代码转化为AST数据库的工具:codeql-cli
- CodeDatabase标准数据库:codeql/vscode-codeql-starter
- QL查询语句编译环境:codeql-cli
- vscode的codeql插件:可视化编写ql查询语句并开发和调试规则
CodeQL原理和相关工具
插件和工具
CodeQL CLI:CodeQL核心引擎也是CodeQL命令行工具 https://github.com/github/codeql-cli-binaries/releases
VSCode插件CodeQL:利用该插件我们可以编写后缀名为.ql
的文件,然后利用其来调用CodeQL CLI来执行我们编写的代码
CodeQL标准库:CodeQL官方提供的.qll
库,在我们编写ql查询语句的时候可以调用该标准库来辅助我们查询数据库,里面甚至包含了别人写好的查询脚本(如:XSS查询脚本)。标准库分为俩个仓库:1.java,cpp等编程语言的CodeQL库;2.go语言单独的codeql库
vscode-codeql-starter:github地址,它也可以说是CodeQL标准库,里面以子模块的方式包含了CodeQL标准库和标准查询的git库。具体就是以下内容
- java,cpp等编程语言的CodeQL标准仓库。因为这些是以子模块的方式存在的,因此你可以随时在不影响你自定义查询的情况下更新它们。
- CodeQL的go语言标准库,也是以子模块呈现
- 有关名为
codeql-custom-queries-<language>
的文件夹,我们可以在文件夹中自定义查询或者调用标准库。文件夹中包含一些查询样例来帮助我们开始自定义查询
标准仓库的选取,我们可以选择codeql,也可以选择vscode-codeql-starter。在vscode的使用方便性来说还是选择vscode-codeql-starter
LGTM.com
CodeQL在线版本LGTM.com,
- 在上面可以直接搜索开源项目,下载数据库
- 也可以上传代码,后台会自动生成代码数据库,然后选定项目进行在线查询
CodeQL工作原理
CodeQL的查询需要建立在一个数据库的基础之上,这个数据库是通过Extractor模块对源代码进行分析、提取后得到的。数据库建立之后,我们就可以使用CodeQL去探索源码,并发现代码中的一些已知问题。
对于编译型语言,CodeQL会在建立数据库时“模拟”编译的过程,在make等编译工具链调用gcc等编译器时,用相同的编译参数调用extractor模块取而代之,收集源代码的所有相关信息,如AST抽象语法树、函数变量类型、预处理器操作等等。对于解释型语言,因为没有编译器的存在,CodeQL会以跟踪执行的方式获取类似的信息。
安装CodeQL
简单叙述一下步骤
- 下载codeql-cli并解压,路径需要时英文 环境变量
- 下载codeql标准库/vscode-codeql-starter
- vscode安装codeql插件并进行setting codeql-cli的路径
Codeql-cli
Code-cli下载:https://github.com/github/codeql-cli-binaries/releases
官网的最新版本已是v2.9.3。个人是windows系统所以下载windows版
下载并解压选定的位置
环境变量配置:为了更方便的使用这一步我们通常需要配置
然后在终端输入codeql,出现以下结果表示配置成功
标准库安装
这俩个标准库二选一。
标准库存放的位置:CodeQL解释器(会通过查找CodeQL CLI所在目录的兄弟目录以及其子目录)来寻找CodeQL标准库的位置,若是没有找到,则需要你手动将CodeQL标准库添加到VSCode的工作区中。添加到VSCode工作区:在vscode菜单栏中,点击文件
->从文件打开工作区
选择上一步克隆项目中的vscode-codeql-starter.code-workspace
文件,即可在codeql-custom-queries-<language>
的文件夹编写.ql
文件进行codeql查询。
codeql标准库
codeql标准库下载:https://github.com/github/codeql/releases
标准库文件需要和Code-cli放在同一目录下。
vscode-codeql-starter
vscode-codeql-starter:https://github.com/github/vscode-codeql-starter/
因为git仓库中包含了子模块,所以我们需要递归下载子模块,我在下面提供了俩种下载方式。另外当子模块有新版本我们需要使用 git submodule update --remote
来更新子模块。(https://blog.csdn.net/qq_37555071/article/details/114260533)
方式1:
git clone --recursive https://github.com/github/vscode-codeql-starter/
# 更新子模块
cd vscode-codeql-starter
git submodule update --remote
方式2:
git clone https://github.com/github/vscode-codeql-starter.git
cd vscode-codeql-starter
git submodule update --init --remote
# 更新子模块
git submodule update --remote
我使用的是方式1下载
codeql插件
打开vscode然后在插件中搜索CodeQL并安装,并进行扩展设置:其中windows上填codeql.cmd或codeql.exe,Linux或Mac系统下填codeql即可。
Linux安装CodeQL
一般可能会需要linux中的CodeQL和windows中的CodeQL联合使用,所以需要尽量保证版本一致
- 安装CodeQL https://github.com/github/codeql-cli-binaries/releases
- CodeQL数据库 https://github.com/github/codeql
- 配置codeql环境变量
codeql --version
生成数据库
原理
编译型语言数据库:CodeQL是根据项目源代码编译的过程中来创建数据库的,所以我们可能会遇到以下问题
- 没有源码的编译型语言(闭源项目),无法从正常途径创建数据库
- 有源码但是编译环境比较难以构建
这就会牵扯到CodeQL生成数据库原理相关知识也是有必要简单了解一下的,我本身没有研究过看到师傅们的文章想记录一下:CodeQL 数据库创建原理分析,使用CodeQL分析闭源Java程序
不用编译就可以生成java数据库的github项目:https://github.com/waderwu/extractor-java
创建数据库4方式
通过codeql插件可以看出添加数据库有四种方式
- 从文件中
- 从压缩包中
- 从url中
- 从LGTM中:https://lgtm.com/
在命令行中使用以下命令可以在本地创建库
codeql database create [OPTION] <database>
:查看codeql创建数据库命令的详细参数
<database>
:创建数据库的路径,这个最终指定的文件夹不存在,但是上级目录需要存在
--language
:创建xx语言的数据库的标识符,CodeQL支持创建以下语言的数据库
语言 | 标识符 |
---|---|
C/C++ | cpp |
C# | csharp |
Go | go |
Java | java |
JavaScript/TypeScript | javascript |
Python | python |
--source-root
:指定创建数据库源文件的根目录,默认情况下命令会将当前目录视为源文件的跟目录
--command
:仅用于编译型语言,用于调用编译器的构建命令。如果不使用该指令的话,CodeQL将会使用内置AutoBuilder自动构建系统。Python和JavaScript不适用该指令。如果是java的话支持项目的编译工具有Maven,Gradle,Ant
--help
:展示命令帮助
codeql database create --language="java" --command="mvn clean install --file pom.xml" --source-root="./commons-collections" cc_database
编译java项目源码生成数据库
Java项目源码无编译生成数据库
编译OpenJDK8并生成数据库
使用CodeQL队JDK进行代码查询,我们可以通过在目标代码库编译生成CodeQL数据库,也可以使用LGTM来获取数据库
Hello World
导入一个CodeQL数据库然后进行Hello World操作。我这里是用的是Commons-Collections的数据库,用哪个数据库都没有关系
-
VsCode中打开CodeQL工作区
-
在CodeQL插件选择并导入数据库
-
在工作区中编写CodeQL语句 右键Run Query即可进行查询
CodeQL语法
概述
QL属于一种静态型语言,其语法其实和SQL比较类似,但是语义基于Datalog(Datalog是一种数据查询语言)
主要是这么几个概念:语法结构,谓词,类,运算符....
推荐还是从官方文档中学习:https://codeql.github.com/docs/ql-language-reference/queries/#queries
还有一些没有列举出来:命名空间
语法结构
结构
在.ql
文件中编写查询模块时,通常包含如下形式的select语句(这样的结构通常在文件末尾):
/* Query metadata */
import /* CodeQL libraries or modules */
/* Optional, define CodeQL classes and predicates */
from [datatype] var /* ... variable declarations ... */
where condition(var = something) /* ... logical formula ... */
select var /* ... expressions ... */
例如:demo.ql
import java
from int i
where i=1
select i
import java
:表示我们要引入的类库,如果我们分析java项目就导入java
from int i
:声明整型变量i
where i=1
:在整型当i等于1的时候,符合条件
select i
:表示输出i
整条语句的思路是在所有的整形数字i中,当i有i等于1的时候,我们输出i
一些其他的细节内容,诸如表达式,运算符.....
from
from
子句通常用来声明查询时候的变量
from <type> <variable name>
from int i
where
where
子句有点类似于if语句,后面需要接可以接一个或读个判定条件
select
select
和sql中的select类似,都是用来输出语句
select i
谓词 Predicates
概述
概述:查询的过程中,我们可以通过运算符和谓词(Predicates)来删选出变量中所需的数据。而谓词的形式类似编程语言中的函数。谓词根据有无返回值可分有返回值谓词和无返回值谓词。谓词按其他方式也可分为非成员谓词,成员谓词,特征谓词
谓词结构:
predicate name(type arg)
{
statements
}
根据上面的结构我们可以整理出谓词的几个要素
- 关键字Predicates(标识没有返回值的谓词),返回值类型(有返回值的谓词)
- 谓词的名称,以小写字母开头的标识符
- 谓词的参数(如果有)以逗号分隔开。对于每个参数,指定参数类型和参数变量的标识符。
- 谓词主体,有大括号包裹的逻辑体
无返回值谓词
无返回值的谓词需要以predicate
关键词开头,若传入的值满足谓词主体中的逻辑,则该谓词将保留该值。我理解的一点是无返回值谓词其实也有返回值,返回值是boolean值,毕竟其可以放在where
子句中
举例:利用谓词从整型数据中删选出1-9的整数
import java
predicate small(int a) {
a in [1..20]
}
from int i
where small(i)
select i
有返回值谓词
如需要从谓词中将结果返回,就需要用谓词中的特殊变量result
。在谓词的主体中,result
变量可以像变量一样正常使用,另外它还可以将变量中的数据返回。那么肯定会有个思考,result
变量的类型是什么由谓词中的声明确定
举例:
import java
int small(int a) {
// 若传入的 i 位于 1-9 内,则返回 i+1
result = a + 1 and a in [1..5]
}
from int i
select small(i)
谓词的递归
谓词和函数一样都是可以递归的
类 Class
概述:CodeQL中的类class表示特定一类的数据集合,定义一个类需要三个要素
- 关键字class
- 起一个类名,类名必须是首字母大写
- 确定类的派生(extends,instanceof)
- 类体需要被
{}
来包裹
派生的类可以是boolean
、float
、int
、string
以及 date
类体:类的主体可以包含:
- 特征谓词的声明:特征谓词类似于类的构造方法,进一步筛选数据
- 任意数量的字段声明。
- 任意数量的成员谓词声明。
当您定义一个类时,该类还从其超类型继承所有非私有成员谓词和字段。您可以覆盖这些谓词和字段,为它们提供更具体的定义。
import java
class SmallInt extends int {
// characteristic predicate 特征谓词
SmallInt() { this = [1 .. 10] }
}
class DivisibleInt extends SmallInt {
//字段
SmallInt divisor;
// characteristic predicate 特征谓词
DivisibleInt() { this % divisor = 0 }
//member predicate 成员谓词
SmallInt getADivisor() { result = divisor }
}
from DivisibleInt i
select i, i.getADivisor()
类型 Types
概述:ql身为一种静态类型的语言,每个变量都必须有它的类型。类型是一组值因此其声明出的变量也是一组值。例如:int是整数集....
ql类型:基本类型,类,字符类型,类域类型,代数数据类型,类型联合,数据库类型
基本类型
基本类型内置于ql中与我们导入的数据库无关
boolean:true和false
float:64位的浮点数,例如6.28,-0.314
int:此类型包含32位二进制补码整数,例如-1and42。
string:此类型包含16位字符的有限字符串。
date:此类型包含日期(和可选的时间
同时ql上还有基本类型的内置表达式操作,比如1.tostring()
表示的就是字符串类型的1
模块 Modules
别名 Aliases
概述:别名是现有QL实体的替代名称。定义别名之后,您可以使用该新名称来引用当前模块中的实体
模块别名:module ModAlias = ModuleName;
类型别名:class TypeAlias = TypeName;
谓词别名:predicate PredAlias = PredicateName/Arity;
变量 Variables
概述:QL中的变量和编程语言中的变量不同,其和使用代数或逻辑中的变量类似,都是代表一组值而这些值会受公式的制约。正因为这样变量表示的是可能包含数据的内存位置。变量的值也会随着时间而改变。变量我们通常都以小写开头
声明变量:所有变量的声明都包含类型和名称。例如:int i,SsaDefinitionNode node。而变量声明的位置可以出现在from子句中,select子句,谓词Predicates中等等
特殊变量
-
this:
this
变量表示的是当前类所包含数据集合 -
result:它是谓词中的特殊变量。在谓词的主体中,
result
变量可以像变量一样正常使用,另外它还可以将变量中的数据返回
表达式 Expressions
概述:表达式是用来计算一组有数据类型值
常量
括号表达式
括号表达式就是用括号括起来的表达式用来增强可读性
范围
表示的就是一个范围,如下表示的局势大于等于N,小于等于M的一组值
[N..M]
常量表达式
表示的就是集合中的一组数值
[N1,N2,N3,N4,N5]
超级表达式
运算符 Formulas
概述:运算符定义表达式中使用的自由变量之间的逻辑关系。通常在类、谓词和select子句的主体中使用运算符来约束它们所引用的值集。例如,i in [0 .. 9]
表示的就是0-9的整数集
比较
等于
Name | Symbol |
---|---|
Greater than | > |
Greater than or equal to | >= |
Less than | < |
Less than or equal to | <= |
类型检查
Name | Symbol |
---|---|
Equal to | = |
Not equal to | != |
所以说Codeql中没有明确的赋值运算符,=
用来表示俩个变量是否相等,除非是在谓词中result
变量中:result = a + 1 and a in [1..5]
以及在类class中的this
变量中
范围检查
逻辑连接词
以下逻辑连接词的优先级从高到底
-
not
-
if ... then ... else
-
and
:来个运算符之间取交集 -
or
: -
implies
:
CodeQL for Java
CodeQL中的Java库由一组ql模块来实现,java.qll
导入了所有核心的Java库模块,我们在查询时可以使用import java
来导入它。
五大类库
Program Elements,程序元素,例如类和方法
AST nodes 抽象树节点,例如语句和表达式
Metadata 元数据,例如注解和注释
metrics,计算指标,例如循环复杂度
Call Gragh,调用图
程序元素 Program Elements
程序元素中的类(class)都直接或间接派生于Element
类,Element
类也称为超类。它提供了常用的成员谓词,用于确定程序元素的名称和检查两个元素是否相互嵌套。直接派生Element
的类包括:包(Package)、编译单元(CompilationUnit)、类型(Type)、方法(Method)、构造函数(Constructor)和变量(Variable)
Element
类中的常见谓词
谓词 Predicates | 作用 effect |
---|---|
hasName(string name) | 筛选数据名称是name的数据 |
getName() | 但会当前数据的名称 |
contains(Element e) |
类型 Type
Type
类有俩个直接派生类PrimitiveType
,RefType
-
PrimitiveType
代表Java中的基础数据类型,派生类有boolean, byte, char, double, float, int, long, short, void, <nulltype>, null -
RefType
代表Java中的引用类型,有派生类Class、Interface、EnumType、Array -
NestedType
代表中的Java中的内部类
类型:RefType
常用的谓词
谓词 Predicates | 作用 effect |
---|---|
getACallable() | 获取所有可以调用方法(其中包括构造方法) |
getAMember() | 获取所有成员,其中包括调用方法,字段和内部类这些 |
getAField() | 获取所有字段 |
getAMethod() | 获取所有方法 |
getASupertype() | 获取父类 |
getAnAncestor() | 获取所有的父类相当于递归的getASupertype*() |
变量 Variable
类Variable
表示Java中的变量,它可以是类的成员字段(无论是否静态),也可以是一个局部变量,或者是函数的参数。因此,有三个子类来满足这些特殊情况的需要。
Field
:字段LocalVariableDecl
:Java中的局部变量和参数.Parameter
:Java方法或构造函数的参数。
抽象语法树 AST
Java中的抽象语法树由俩个类来表示:Expr
,Stmt
- 就是AST的表达式,例如
if(a=b)
,中的a=b - 就是AST的语句,例如
if(a=b)
中的if就是一个IfStmt
元数据 Metadata
除了Java程序代码本身之外,Java程序还有中的注解(Annotations)和 Javadoc注释这些比较重要的内容。由于这些元数据对于加强代码分析或者是作为分析目标本身都很有用处,因此,CodeQL库定义了用于访问这些元数据的类。
涉及到Annotation
和javaDoc
类
指标 Metrics
调用图 Call graph
简单理解就是数据库中有关Java代码中的调用关系,主要涉及俩个类Call
和Callable
Call
存储在Expr.qll
库中,对应Java中的调用表达式,new
表达式和使用this
或super
的显式构造函数调用Callable
存储在Member.qll
库中,表示Java Class里方法
Call
类中常用的俩个谓词getCallee()
和getCaller()
例如:这里的request.getHeader("x-requested-with")
就是对应一个Call
- getCallee():获取调用这个函数调用块对应的Callable,例如图中的这个就是
getHeader
这个Callalbe - getCaller():获取这个函数调用块的父函数调用块,即找那一块函数调用里调用了这个块
Callable
类表示方法和构造函数
实际应用
上面介绍的是Codeql关于Java库的大体框架。而以下会介绍真正应用中我们常用的查询手法,多以CTF题目中应用为主
- Method 方法类,Method method表示获取当前项目中所有的方法
- MethodAccess 方法调用类,MethodAccess call表示获取当前项目当中的所有方法调用
- Parameter 参数类,Parameter表示获取当前项目当中所有的参数节级元素
限制类
常见·速查
TypeSerializable
类型,代表JDK中的Serializable接口TypeConstructor
类型,代表Constructor类Class
类型,数据库中的类instanceof
运算符,继承/实现/实例化Class.fromSource
Class
的谓词,用于排除JDK自带的类Class.getASupertype
Class
的谓词,用于递归父类类型RefType.hasQualifiedName(string packageName, string className)
RefType
的谓词,用来识别类型
是否来自package.classNameClassInstanceExpr.getType
ClassInstanceExpr
的谓词,用来获取实例化化对象所在的类Method.get
Reference
https://blog.csdn.net/weixin_44689968/article/details/115561093