csdn上阅读体验更加,这个目录太长这里用着不太舒服
https://blog.csdn.net/qq_72685284/article/details/144118176
网上对于codeql的基础讲解的比较少,很多都是直接从codeql for java 或者直接拿一个靶场开始练习的。我通过对官方文档的一些翻译和加上自己的一些个人的理解和丰富合适的例子。帮助新人对codeql有一个基础的认识。因为好多英文名词并没有比较统一的翻译,所以我这里会标注一些关键的英文词的中文含义,很多意思还需要大家从英文的原文中体会其中的含义,同时方便大家结合文档进行查看。
参考文档。
https://codeql.github.com/docs/ql-language-reference/about-the-ql-language/
declarative languages
声明式(declarative)是结果导向的,命令式(imperative)是过程导向的。它们都有自己适用的场景和局限,于是现实中的编程语言常常都有两者的身影。
declarative 案例 : SQL, HTML ,codeql
imperative 案例 : C, C++, Java
QL is a declarative, object-oriented(面向对象的) query language that is optimized(优化) to enable efficient analysis of hierarchical data structures, in particular, databases representing software artifacts.
The syntax(语法) of QL is similar to SQL, but the semantics(语意) of QL are based on Datalog, a declarative logic programming language often used as a query language. This makes QL primarily a logic language, and all operations in QL are logical operations. Furthermore, QL inherits recursive(递归) predicates(谓语) from Datalog, and adds support for aggregates(聚合), making even complex queries concise(简洁) and simple.
总之就是codeql的语法和常规的编程语言不太一样,他是基于datalog的,不能拿C,java 那类语言去类比,有很多的概念和专业用语都是不太一样的。
Object orientation(面向对象) is an important feature of QL. The benefits of object orientation are well known – it increases modularity(模块化), enables information hiding, and allows code reuse(复用). QL offers all these benefits without compromising(妥协) on its logical foundation. This is achieved by defining a simple object model where classes are modeled as predicates and inheritance as implication. The libraries made available for all supported languages make extensive use of classes and inheritance.
与sql相比,codeql的语言功能更加丰富,增加了面相对象的特性。可以自定义谓词。
谓词Properties
谓词用于描述构成 QL 程序的逻辑关系。
严格来说,谓词求值为一组元组。例如,考虑以下两个谓词定义:
1 | predicate isCountry(string country) { |
谓词 isCountry 返回的是一组一元组 {(“Belgium”),(“Germany”),(“France”)},而 hasCapital 是一组二元组 {(“Belgium”,”Brussels”),(“Germany”,”Berlin”),(“France”,”Paris”)}。这些谓词的元数分别为 1 和 2。
通常,谓词中的所有元组都具有相同数量的元素。谓词的 arity 就是元素的数量,不包括可能的结果变量。
QL 中有许多内置谓词。您可以在任何查询中使用这些谓词,而无需导入任何其他模块。除了这些内置谓词之外,您还可以定义自己的谓词:
谓词组成
- 谓词的关键字predicate,或者谓词的返回值。(谓词可以分为without result 和 with result)
- 谓词名词
- 谓词参数
- 谓词主体
without result
开头那两个谓词也是 without result 的
1 | predicate isSmall(int i) { |
with result
您可以通过将关键字 predicate 替换为结果类型来定义带有结果的谓词。这引入了特殊变量 result。result的值就是我们的返回值。
这个from就是定义一个变量。
1 | int getSuccessor(int i) { |
可以在谓词主体中调用其他的谓词
1 | Person getAChildOf(Person p) { |
谓词还可能对其参数的每个值产生多个结果(或根本没有结果)
1 | string getANeighbor(string country) { |
Recursive(递归) predicates
1 | string getANeighbor(string country) { |
Kinds(种类) of predicates
谓词有三种类型,即Non-member谓词、member谓词和Characteristic谓词。
非成员谓词是在类之外定义的,也就是说,它们不是任何类的成员。
1 | int getSuccessor(int i) { // 1. Non-member predicate |
Binding behavior
It must be possible to evaluate a predicate in a finite(有限的) amount of time, so the set it describes is not usually allowed to be infinite(无限的). In other words, a predicate can only contain a finite number of tuples.
这里的无限其实也不是无穷大或者无穷多。上面和下面的那些反例其实也就是多了一点判断条件。
1 | int getSuccessor(int i) { |
1 | /* |
Binding sets
如果一定要用infinite的谓词。
在这种情况下,您可以使用 bindingset 注释指定显式绑定集。此注释适用于任何类型的谓词。
1 | bindingset[i] |
也可以多个,这种形式的意思是。x,y至少有一个是bound的。和bindingset[x, y]不同,意味着两个都是bound的
- If
x
is bound, thenx
andy
are bound. - If
y
is bound, thenx
andy
are bound.
1 | bindingset[x] bindingset[y] |
后者可以用于两种不同类型的情况
1 | bindingset[str, len] |
查询Queries
1 | from /* ... 声明变量 ... */ |
除了“表达式”中描述的表达式之外,您还可以包括:
as 关键字,后跟名称。这为结果列提供了“标签”,并允许您在后续的选择表达式中使用它们。
order by 关键字,后跟结果列的名称,以及可选的关键字 asc 或 desc。这决定了显示结果的顺序。
from 和 where 部分是可选的。
除了“表达式”中描述的表达式之外,您还可以包括:
- as 关键字,后跟名称。这为结果列提供了“标签”,并允许您在后续的选择表达式中使用它们。
- order by 关键字,后跟结果列的名称,以及可选的关键字 asc 或 desc。这决定了显示结果的顺序。
这些和sql都差不多,不同地方在于是,这里的from可以声明变量。
例子
1 | from int x, int y |
Query predicates
1 | int getProduct(int x, int y) { |
这个查询谓语返回以下结果
x | y | result |
---|---|---|
3 | 0 | 0 |
3 | 1 | 3 |
3 | 2 | 6 |
编写查询谓词而不是 select 子句的一个好处是,您也可以在代码的其他部分调用该谓词。相比之下,select 子句就像一个匿名谓词,因此您无法稍后调用它。
Types
Primitive types
有以下这些类型。
boolean,float,int,string,date。
QL 在原始类型上定义了一系列内置操作,例如,1.toString() 是整数常量 1 的字符串表示形式。有关 QL 中可用的内置操作的完整列表,请参阅 QL 语言规范中的内置部分。
https://codeql.github.com/docs/ql-language-reference/ql-language-specification/#built-ins
此外,QlBuiltins::BigInt 中还有一个实验性的任意精度整数原始类型。默认情况下,此类型在 CodeQL CLI 中不可用,但可以通过将 –allow-experimental=bigint 选项传 来启用它。
Classes
定义一个类
- class 关键字
- class 名,要求首字母大写
- 通过 extends和instanceof定义的supertypes
- 括号括起来的类体
1 | class OneTwoThree extends int { |
定义了一个类 OneTwoThree,它包含值 1 2 3。
OneTwoThree 继承了 int,也就是说,它是 int 的子类。QL 中的类必须始终具有至少一个supertypes。使用 extends 关键字引用的supertypes称为base types of the class。
Class bodies
The body of a class can contain:
- A characteristic predicate declaration. // 前面讲 谓词种类 的时候都提到了 #Kinds(种类) of predicates
- Any number of member predicate declarations.
- Any number of field declarations.
Characteristic predicates
相当于构造方法。前面提到过
Member predicates
可以用这种方法调用一个member predicate
1 | (OneTwoThree).getAString() |
Fields
在body of a class中进行声明,例子如下。
1 | class SmallInt extends int { |
Concrete classes
比如上面那个 DivisibleInt 有一个 SmallInt 的 Fields
下面这些就和面向对象的语言很相似,看一下名字就知道是什么意思。不细讲。
Abstract classes
抽象类
Overriding member predicates
重写父类谓词
Multiple inheritance
多继承
Final extensions
类似于final关键字。
Non-extending subtypes
其实就是有点像接口,下面这个会报错。instanceof 声明的超类型中的字段和方法不会成为子类的一部分
1 | class Foo extends int { |
1 | class Interface extends int { |
Modules
之类就给一个例子吧,太复杂的官方也没有给例子,这就是好多语言都有的一个东西。
OneTwoThreeLib.qll
1 | class OneTwoThree extends int { |
OneTwoQuery.ql
1 | import OneTwoThreeLib |
在对chat的逼问之下,还是把这个调用方法给问出来了。
OneTwoQuery2.ql
1 | // 导入库文件 |
Signatures
这个概念其实比较抽象。我暂时不想详细的写。这里就直接给官网的例子了。但是手边暂时没有具体使用的案例。
Predicate signatures
注意这个分号
1 | signature int operator(int lhs, int rhs); |
Type signatures
1 | signature class ExtendsInt extends int; |
Module signatures
1 | signature module MSig { |
Parameterized module signatures
1 | signature class NodeSig; |
Aliases
Defining an alias
Module aliases
1 | module ModAlias = ModuleName; |
下面这个会弃用oldversion。
1 | deprecated module OldVersion = NewVersion; |
Type aliases
1 | class TypeAlias = TypeName; |
你可以使用别名将基本类型boolean的名称缩写为Bool:
1 | class Bool = boolean; |
在OneTwoThreeLib中使用模块M中定义的类OneTwoThreeLib.qll,你可以创建一个别名来使用更短的名称OT
1 | import OneTwoThreeLib |
Predicate aliases
1 | int getSuccessor(int i) { |
可以为这个谓语设置一个这样的别名。
1 | predicate succ = getSuccessor/1; |
Strong and weak aliases
有annotation 注解的是 Strong aliases。来自相同module/type/predicate的weak aliases定义之间的别名歧义是允许的,但来自不同Strong aliases定义之间的别名歧义是无效的QL。
Every alias definition is either strong or weak. An alias definition is strong if and only if it is a type alias definition with annotation
final
. During name resolution, ambiguity between aliases from weak alias definitions for the same module/type/predicate is allowed, but ambiguity between between aliases from distinct strong alias definitions is invalid QL. Likewise, for the purpose of applicative instantiation of parameterised modules and :ref:`parameterised module signatures, aliases from weak alias definitions for instantiation arguments do not result in separate instantiations, but aliases from strong alias definitions for instantiation arguments do.
Variables
Declaring a variable
其实前面就一只在遇见了。声明,赋值,输出。
1 | from int i |
Free and bound variables
1 | "hello".indexOf("l") |
第一个没有 variables ,值是2
第二个bound variable ,值恒为3
第三个free variables ,i 的值影响着整体的值。表达式是否成立取决于i的值。
1 | "hello".indexOf("l") = 1 |
第一个永远不成立。
第二个永远成立
第三个可能成立
第四个单y为负数时永远不成立
Expressions
Literals
Integer literals
1 | 0 |
Float literals
1 | 2.0 |
等等
Parenthesized(括号) expressions
Ranges
其实前面也见到一些了
[3 .. 7]
表示的是3到7的整数
Set literal expressions
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
是一个集合参数
Super expressions
这个直接看例子,其实就是super. 然后找父类。
1 | class A extends int { |
Calls to predicates
调用类的方法
例如 a.getAChild() 是调用的 a 的谓词 getAChild()
Aggregations
1 | <aggregate>(<variable declarations> | <formula> | <expression>) |
1 | // 内容超过500行的文件 |
Evaluation of aggregates
以这个为例
1 | select sum(int i, int j | |
步骤
确定输入变量:这是聚合表达式中声明的变量,包括在聚合内声明的变量和在聚合外部使用的变量。
生成所有可能的元组(组合):这些元组是输入变量的所有可能值的组合,必须满足给定的条件公式。
**应用
**:对每个元组应用 ,并收集生成的值(可能有多个不同的值)。 应用 aggregates function:使用 aggregates function(如 sum、count 等)对第3步中生成的值进行处理,计算最终结果。
1. 确定输入变量:
•输入变量是 i 和 j,分别代表 “hello” 和 “world!” 中的字符位置。
2. 生成所有可能的元组:
•我们通过 exists(string s | s = “hello”.charAt(i)) 和 exists(string s | s = “world!”.charAt(j)) 来生成所有可能的 (i, j) 对,表示字符串 “hello” 和 “world!” 中字符的位置。
•”hello” 有 5 个字符(0, 1, 2, 3, 4),而 “world!” 有 6 个字符(0, 1, 2, 3, 4, 5)。
•所以所有可能的 (i, j) 对是:
1 | (0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), |
3. 应用
•在这个查询中,聚合表达式是 i。我们从每个元组中选择 i 的值。
•例如,所有 30 个元组中,i 的值会分别为:
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4
4. 应用 aggregates function:
0 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + 1 + 1 + 1 + 1 + 2 + 2 + 2 + 2 + 2 + 2 + 3 + 3 + 3 + 3 + 3 + 3 + 4 + 4 + 4 + 4 + 4 + 4
值为60。
如果为这种
1 | select sum(int i, int j | |
1 | 0 + 0 = 0 |
值为135
按照那4步来就行。
Omitting parts of an aggregation
其实有些地方可以适当简写。
- 省略
和
1 | <aggregate>(<type> v | <expression> = v | v) |
例如
1 | count(int i | i = "hello".indexOf("l") | i) |
• “hello”.indexOf(“l”) 直接返回所有 “l” 的索引位置(2, 3)。
• count 统计这些索引的个数,因此两种写法等价。
- 省略
如果只有一个aggregation variable,则可以省略 aggregation variable,此时表达式默认为该变量本身。
1 | avg(int i | i = [0 .. 3] | i) |
- 在 count 中,即使有多个aggregation variable,也可以省略
,此时表达式默认为常量 1,即统计满足条件的所有元组数量。
1 | count(int i, int j | i in [1 .. 3] and j in [1 .. 3] | 1) |
- 省略
,仅保留两个竖线 ||
1 | <aggregate>(<variable declarations> | | <expression>) |
1 | max(File f | | f.getTotalNumberOfLines()) |
这段代码的意思是:统计数据库中所有文件的最大行数。
- 省略
和
1 | count(File f | any() | 1) |
count(File f) 直接统计数据库中的文件数量。
Monotonic(单调) aggregates
直接看官方给的例子吧。
1 | string getPerson() { result = "Alice" or |
variant | person | cost |
---|---|---|
default | Alice | 201 |
default | Bob | 100 |
default | Charles | 100 |
default | Diane | 0 |
monotonic | Alice | 101 |
monotonic | Alice | 200 |
monotonic | Bob | 100 |
monotonic | Diane | 0 |
标准聚合 和 单调聚合 在处理公式
标准聚合:
•对每个由
•然后对这个列表应用聚合函数,例如 sum、count 等。
单调聚合:
•对每个由
•对每种组合分别应用聚合函数。
结果差异:
•标准聚合 通常返回一个结果,表示所有值的总和、计数等。
•单调聚合 会返回多行结果,表示每种可能组合的聚合值。
场景 1: 缺少
•如果
•标准聚合 会忽略这个缺失值,计算其他值的结果。
•单调聚合 不会计算结果,因为缺失值使得无法创建完整的组合。
场景 2: 多个
•如果
•标准聚合 将所有
•单调聚合 会生成多种组合,对每种组合分别计算结果。
Recursive monotonic aggregates
暂时不讲解
Any
Expression | Values |
---|---|
any(File f) |
all File s in the database |
`any(Element e | e.getName())` |
`any(int i | i = [0 .. 3])` |
`any(int i | i = [0 .. 3] |
Unary operations
1 | -6.28 |
Binary operations
1 | 5 % 2 |
Casts(类型转换)
1 | import java |
Don’t-care expressions
其实这个符号在其它语言也经常见
1 | from string s |
Formulas 公式
Comparisons 比较
这里提一下。
就是一个等号在这里代表的就是Equal to
Not equal to就是 !=
定义的话用的是from
Type checks
类型检查,和java里面的instanceof功能差不多
<expression> instanceof <type>
Range checks
检查范围
A range check is a formula that looks like:<expression> in <range>
x in [2.1 .. 10.5]
Calls to predicates
谓词调用。
Parenthesized formulas
杯括号包裹起来的 formulas
Explicit quantifiers显示量词
exists
都很好理解。
This quantifier has the following syntax:
1 | exists(<variable declarations> | <formula>) |
You can also write exists(<variable declarations> | <formula 1> | <formula 2>)
. This is equivalent to exists(<variable declarations> | <formula 1> and <formula 2>)
.
This quantified formula introduces some new variables. It holds if there is at least one set of values that the variables could take to make the formula in the body true.
For example, exists(int i | i instanceof OneTwoThree)
introduces a temporary variable of type int
and holds if any value of that variable has type OneTwoThree
.
forall
This quantifier has the following syntax:
1 | forall(<variable declarations> | <formula 1> | <formula 2>) |
forall
introduces some new variables, and typically has two formulas in its body. It holds if <formula 2>
holds for all values that <formula 1>
holds for.
For example, forall(int i | i instanceof OneTwoThree | i < 5)
holds if all integers that are in the class OneTwoThree
are also less than 5
. In other words, if there is a value in OneTwoThree
that is greater than or equal to 5
, then the formula doesn’t hold.
Note that forall(<vars> | <formula 1> | <formula 2>)
is logically the same as not exists(<vars> | <formula 1> | not <formula 2>)
.
forex
This quantifier has the following syntax:
1 | forex(<variable declarations> | <formula 1> | <formula 2>) |
This quantifier exists as a shorthand for:
1 | forall(<vars> | <formula 1> | <formula 2>) and |
In other words,
forex
works in a similar way toforall
, except that it ensures that there is at least one value for which<formula 1>
holds. To see why this is useful, note that theforall
quantifier could hold trivially. For example,forall(int i | i = 1 and i = 2 | i = 3)
holds: there are no integersi
which are equal to both1
and2
, so the second part of the body(i = 3)
holds for every integer for which the first part holds.Since this is often not the behavior that you want in a query, the
forex
quantifier is a useful shorthand.
Implicit quantifiers
相当于我们上面提到的 Don’t-care expressions。
Logical connectives
- Negation (not)
- Conditional formula (if…..then…else)
- Conjunction (and)
- Disjunction (or)
- Implication (implies)
any()
none()
not
……
Annotations
有点像java里面的修饰符。
像 abstract
deprecated
final
这些前面都提到过。
Lexical syntax
Comments
有两种注释方法。
1 | /** |
Name resolution(解析)
Names
处理一个name首先在当前模块的命名空间中查找名称。
如果是import语句。name resolution会更加复杂,看下面这个例子。
1 | import javascript |
编译器首先检查库模块javascript.qll,再采取下面说到的那些步骤。如果失败,它会检查在Example.ql的模块命名空间中定义的名为javascript的explicit module。
Qualified references
限定引用是一种模块表达式,使用 . 作为文件路径分隔符。它只能在 import 语句中使用,用于导入由相对路径定义的库模块。比如我们Example.ql有如下一个import语句。
1 | import examples.security.MyLibrary |
1. 当前目录查找 编译器首先从包含当前 Example.ql 文件的目录中查找目标文件 examples/security/MyLibrary.ql
2. 查询目录查找 查找相对路径 examples/security/MyLibrary.qll。如果查询目录未配置或路径中未找到目标文件,继续下一步。查询目录是第一个包含qlpack.yml文件的目录。(或者,在老版本中,是一个名为queries.xml的文件。)
**3. 库路径查找 ** 查看qlpack.yml 文件中的 libraryPathDependencies 设置
4. 查找失败 如果还找不到编译会报错。
Selections
1 | <module_expression>::<name> |
Example
CountriesLib.qll
1 | class Countries extends string { |
可以用如下方式使用
1 | import CountriesLib |
1 | import CountriesLib::M |
Namespaces
命名空间。和其它语言比较像,不过多解释。
Global namespaces
Local namespaces
QL language specification
语言规范太多了,这里就不细讲了。
https://codeql.github.com/docs/ql-language-reference/ql-language-specification/