为了使用CodeQL进行定制分析,我们可以通过自己编写查询来实现查找漏洞或错误。CodeQL的查询类型有:
用CodeQL编写的查询文件扩展名为.ql
,并包含一个select
子句。许多现有查询都包含其他可选信息,并具有以下结构:
/\*\*
\*
\* Query metadata
\*
\*/
import /\* ... CodeQL libraries or modules ... \*/
/\* ... Optional, define CodeQL classes and predicates ... \*/
from /\* ... variable declarations ... \*/
where /\* ... logical formula ... \*/
select /\* ... expressions ... \*/
每个查询通常包含一个或多个import
语句,这些语句定义了要导入到查询中的库或模块。库和模块提供了一种将相关类型,谓词(用于描述组成QL程序的逻辑关系)和其他模块组合在一起的方法。然后,查询可以访问您导入的每个库或模块的内容。
该from
子句声明查询中使用的变量。每个声明必须采用 <variable name>
的形式。
该where
子句定义了适用于该from
子句中声明的变量以生成结果的逻辑条件。该子句使用聚合,谓词和逻辑公式将目标变量限制为较小的集合,这些集合满足已定义的条件。
该select
子句指定要显示的变量,这些变量需符合where
子句中定义的条件。select子句的有效结构由元数据中指定的@kind
属性定义。
告警查询(@kind problem
)的Select子句由两“列”组成,其结构如下:
select element, string
element:查询标识的代码元素,用于定义显示告警的位置。
string:一条消息,其中还可以包含链接和占位符,说明生成告警的原因。
您可以修改select
语句最后一栏中定义的告警消息,使用有关链接或占位符,提供通过查询所找到的告警或路径的更多详细信息。
每个查询文件都是以查询元数据作为开始,它为用户提供有关查询的信息,并告诉CodeQL CLI如何处理查询结果。
我们在使用database analyze
命令进行查询时,必须包括以下两个属性,以确保得到正确的查询结果:
所有查询文件都支持以下属性:
属性
值
描述
@description
用一段简单的话描述查询的目的以及查询结果_的重要性_。该描述以纯文本编写,并使用单引号('
)将代码括起来。
@id
是一串小写字母或数字,通过/
或-
分隔构成的单词序列,用于识别和分类查询。每个查询必须具有唯一的 ID。为确保这一点,对每个ID使用固定的结构可能会有所帮助。例如,标准LGTM查询具有以下格式:/
。
@kind
problem
path-problem
标识查询是告警(@kind problem
)还是路径(@kind path-problem
)。有关这些查询类型的更多信息,请参见关于CodeQL查询。
@name
定义查询标签的语句。该名称以纯文本形式编写,并使用单引号('
)将代码括起来。
@tags
correctness
maintainability
readability
security
这些标签将查询按大类分组在一起,以使其更易于搜索和识别。除了此处列出的常用标签外,还有许多更具体的类别。有关更多信息,请参阅《 查询元数据样式指南》。
@precision
medium
high
very-high
表示查询结果为“真肯定”(相对于“假肯定”结果)的百分比。这与@problem.severity
属性一起确定默认情况下是否在LGTM上显示结果。
@problem.severity
error
warning
recommendation
定义查询生成的任何警报的严重性级别。这与@precision
属性一起确定默认情况下是否在LGTM上显示结果。
过滤器查询用于定义其他约束,以限制其他查询返回的结果。筛选器查询必须具有与@kind
要筛选其结果的查询相同的属性。不需要其他元数据属性。
一个标准的Java查询元数据:
有关查询元数据的更多示例,请参见GitHub存储库中的标准CodeQL查询。
您可以通过修改查询的select
语句来控制分析结果在源代码中的显示方式。
告警查询必须在元数据中定义@kind problem属性。有关更多信息,请参见CodeQL查询的元数据。select
语句最基本的形式必须包含两“列”:
我们以一个使用标准CodeQL CodeDuplication.qll
库识别相似文件的简单查询作为示例。
import java
import external.CodeDuplication
from File f, File other, int percent
where similarFiles(f, other, percent)
select f, "This file is similar to another file."
这个基本的select语句有两列:
select语句定义的告警信息是固定的,不能给用户提供太多信息。由于查询识别出了相似文件(other
),因此很容易扩展select
语句以告知用户这些相似文件的名称。例如:
select f, "This file is similar to " \+ other.getBaseName()
尽管这比原始的select语句提供了更多信息,但用户仍然需要手动查找其他文件。
您可以在告警信息中使用占位符以插入其他信息,例如,指向相似文件的链接。占位符使用$@定义
,并使用select语句的后两列中的信息来填充。例如,下述select语句就返回了4列信息:
select f, "This file is similar to $@.", other, other.getBaseName()
显示告警消息时,$@
占位符将替换为根据该select
语句定义的第三第四列内容创建的链接。
如果使用$@
在说明信息中多次使用占位符标记,则第N
个使用的$@
将由2N+2列
和2N+3
列组成的链接代替。如果附加列数量多于占位符标记,则末尾的列将被忽略。相反,如果附加列数量少于占位符标记,则末尾的$@
标记将被视为普通文本,而不是占位符。
我们可以更进一步,更改select
语句以报告两个文件中相似的内容部分,因为这部分信息已经在查询中可用。例如:
select f, percent \+ "% of the lines in " \+ f.getBaseName() \+ " are similar to lines in $@.", other, other.getBaseName()
由于此处添加的新元素不需要是可单击的,因此我们可以将它们直接拼接到字符串信息中。
我们编写的查询应该保存在自定义QL包文件夹或其子目录中。QL包用来整理在分析时使用的文件,包括查询、库文件、查询套件和重要的元数据。QL包目录的根目录下必须包含一个以qlpack.yml
命名的文件,该文件包含name、version以及libraryPathDependencies字段属性。如果这个包还包含查询套件,可以使用suites这个字段来指定其位置。
执行命令的时候,codeql会扫描其安装目录或者子目录查找qlpack.yml文件,文件中的元数据会告知codeql如何编译查询、依赖什么库以及查询套件所在的位置。
例如一个自定义C++查询和库的QL包的qlpack.yml文件可能包含以下内容:
name: my-custom-queries
version: 0.0.0
libraryPathDependencies: codeql\-cpp
suites: my\-custom-suites
其中codeql-cpp是codeql仓库中用于C/C++分析的QL包的名称。
QL包包含的其它内容(codeql分析时使用的查询和库文件)与qlpack.yml放在同一个目录,或者它的子目录中。
例如,一个包含如下文件目录的QL包,如果需要导入CustomSinks.qll只需要在包的任意目录声明:
import mycompany.java.CustomSinks
qlpack.yml
mycompany/
java/
security/
CustomSinks.qll
Security/
CustomQuery.ql
我们可以通过如下方式使用自定义的查询:
codeql database analyze xssdb E:\\codeql\\codeql-repo\\customql\\java\\xss.ql --format=sarif-latest --output=result.sarif
codeql database analyze xssdb E:\\codeql\\codeql\-repo\\customql\\java\\xss.ql --format=csv --output=codeql.csv