CodeQL规则编写之常用类与特殊情况
K9weirdo 历史精选 592浏览 · 2025-03-24 07:27

关于CodeQL的使用与规则编写许多师傅都写了文章进行分享,但是关于编写任何规则时经常会用到的一些类和谓词,以及代码分析时可能会出现的常见特殊情况(漏报、误报、断链等)的分析文章相对较少。所以我在使用新版本Codeql时总结了一下相关的内容写了一篇文章,欢迎各位师傅继续补充内容。下面内容主要基于Java语言项目。

前言

Codeql中的Java类库

关于Codeql中的Java类库,我按照继承关系简易列了一下Codeql中的常用且核心的一些类的继承图

Snipaste_2025-03-21_15-43-37.jpg


下面总结了其中一些用的较多的类以及谓词,其余没列举的多为子类或者顶层父类。

PS:如果遇到不熟悉的类一定要多阅读Codeql官方文档以及官方规则库

常用类及谓词

1、Expr类

作用

Expr类用于处理 Java 语言(以及其他编程语言)中的表达式。这包括了从简单的变量引用、字面量到复杂的方法调用和运算表达式等,在Java语言中,表达式是由变量或常量与符号的组合。Expr是分析程序中运算和数据流动的关键组件

常用子类

该类的子类继承了父类的大部分谓词,下面列举了一些常用的特征性比较明显的子类

LambdaExpr:该类表示隐式类实例创建的表达式(Lambda 表达式),这些表达式实例化一个匿名类,该类将覆盖由其函数接口类型指定的唯一方法。asMethod()是该类常用谓词,用于获取与当前 lambda 表达式对应的隐式方法。

ClassInstanceExpr:ClassInstanceExpr类代表了 Java 代码中的类实例化表达式,通常是使用new关键字创建对象的表达式。它具体表示构造器调用,从而创建了一个新的类实例。 getConstructor()是该类常用谓词,用于获取该类实例创建时调用的构造函数。

ConditionalExpr:ConditionalExpr类用来表示形如 a ?b :c 的条件表达式。getCondition()、getTrueExpr()和getFalseExpr()是该类常用谓词,分别表示此条件表达式的条件、此条件表达式的条件计算结果为 true和 false 时计算的表达式

常用谓词

示例代码



上面示例使用如下规则查询



定义的一个表达式Expr e,则e结果如下



1getEnclosingCallable()

获取出现此表达式的最近可调用对象(如果存在),返回的结果为Callable类型

e.getEnclosingCallable()的结果如下



2getEnclosingStmt()

用于获取包含当前表达式的最近的语句。可以理解表达式在代码中的位置以及它是如何被使用的

语句可以是任何形式的执行单元,例如赋值语句、返回语句、调用语句、条件语句等。返回的结果为Stmt类型

e.getEnclosingStmt()的结果如下



3getControlFlowNode()

将表达式与其在控制流图中的节点相关联。这对于进行控制流分析和数据流分析非常关键。返回的结果为ControlFlowNode类型

e.getControlFlowNode()结果如下



4getParent()

返回包含此表达式的更高级别的语法结构(如另一个表达式或一个语句)。返回的结果为ExprParent类型

此处e.getParent()结果如下



5getAChildExpr()

返回方法调用的子表达式。返回的结果为Expr类型

e.getAChildExpr()结果如下,为一个MethodCall



e.getAChildExpr().getAChildExpr()的结果存在三个,如下



6getType()

获取此表达式的类型,返回的结果为Type类型

e.getType()结果为



7toString()

用于将表达式转换成一个字符串,常用于进行某种指定的判断。返回的结果为string类型

如果写where时发现某些判断调节不好写,可以尝试用toString()将条件转为字符串进行等号比较



同理,此处e.toString()的结果为 e 的字符串结果

2、Variable类

作用

Variable类代表 Java 程序中的变量,包括局部变量、字段(即类成员变量)、参数等。这个类是对变量的抽象,使得分析变量的定义、使用和作用域变得可行。

常用子类

该类的子类继承了父类的大部分谓词,下面列举了一些常用的特征性比较明显的子类

LocalVariableDecl:该类表示局部变量的声明

LocalScopeVariable:LocalScopeVariable类本地范围的变量,即局部变量或参数

常用谓词

示例代码



上面示例使用如下规则查询



上面示例查询时均定义Variable vv如下





1getAnAssignedValue()

用于返回给变量分配的任意一个值的表达式。返回的结果为Expr类型

v.getAnAssignedValue()结果如下



2getAnAccess()

返回 任意一个访问、调用、读取或写入此变量的表达式,想要分析所有访问特定变量的位置,可以使用该谓词。返回的结果为Expr类型

v.getAnAccess()结果如下



3getInitializer()

如果变量在声明时被初始化,则返回这个初始化表达式。这对于分析字段或局部变量的初始状态非常有用。返回的结果为Expr类型

v.getInitializer()结果如下





4getName()

返回变量的名称。这个谓词非常实用,尤其在你需要根据变量的名称进行特定的检查或分析时。返回的结果为string类型

此处v.getName()的结果是"result"



3、Annotation 类

作用

主要用来表示代码中的注解(Annotations),如 Java 代码里的 @Override@RequestMapping 等,CodeQL 将源代码中的注解抽象为 Annotation 对象,能够对其属性、参数和位置进行访问和分析。

该类继承Expr

常用方法

示例代码



上面示例使用如下规则查询



定义Annotation a,则a结果如下



这里我们选择@RequestHeader进行后续分析

1getType()

获取此注解的注解类型声明,返回的结果为AnnotationType类型

a.getType()的结果如下



2getValue(name)

该谓词可以直接获取具有指定名称的注解元素的值,也同时包括未显式指定对应值时的默认值。返回结果为Expr类型

a.getValue("name")a.getValue("required")结果如下,注意,此处的率筛选出来的name的值是包括引号的"testId"



3getAnnotatedElement()

该谓词返回的是被注解的元素,返回结果为Element类型

则此处a.getAnnotatedElement()值如下







4、Callable

作用

Callable指一个可调用对象,包括一个方法或构造函数。这类是对可调用对象的抽象,另外两个常用的类MethodConstructor类均继承于该类,并且继承了该类的大部分的谓词

常用谓词

示例代码



上面示例使用如下规则查询



定义Callable ca,则ca结果如下





1getAnAnnotation()

获取应用于此可调用对象(如:方法)的注释,也包括继承的注释。其中只包括直接注释,不包括间接注释,返回的结果为Annotation类型。MethodConstructor类可用。

则上述代码中ca.getAnAnnotation()的结果如下



2getAParameter()

用于返回此可调用对象的形参,返回的结果为Parameter类型。MethodConstructor类可用。

则上述代码中ca.getAParameter()的结果如下,存在两个结果



3getDeclaringType()

用于获取在其中声明此可调用对象的所在类型,返回的结果为RefType类型。MethodConstructor类可用。

则上述代码中ca.getDeclaringType()的结果如下



4getACallee()

用于获取当前可调用对象可以调用的可调用对象。例如:如果当前可调用对象是一个方法的话,可以获取当前方法中的被调用的那个方法本身。返回的结果为Callable类型。MethodConstructor类可用。

则上述代码中ca.getACallee()的结果如下



5getQualifiedName()

获取此可调用对象的完整限定名称,适用于规则调试。但常见使用过程中条件判断时,建议使用 ca.getDeclaringType().hasQualifiedName(包名,类名)MethodConstructor类可用。

则此处输出的结果为字符串com.example.demo.controller.FileOperateController.readFile6



5、Call 类

作用

Call类代表的是程序中所有的可调用对象的调用,包括方法的调用、构造函数和超级构造函数的调用,以及通过类实例化调用的构造函数的调用。另外两个常用的类MethodCallConstructorCall类均继承于该类,并且继承了该类的一部分的谓词

常用谓词

示例代码



上面示例使用如下规则查询



定义Call c,则c结果如下



1getCallee()

获取当前调用当中的可调用对象。返回的结果为Callable类型。MethodConstructor类可用。

c.getCallee()的结果为



2getCaller()

获取调用当前调用的可调用对象。返回的结果为Callable类型。MethodConstructor类可用。

c.getCaller()的结果为



3getAnArgument()

这个谓词返回方法调用中的任意一个参数。返回的结果为Expr类型。

当你不需要关注特定参数的位置,但想要分析所有可能的参数时可以使用该谓词。MethodConstructor类可用。

c.getAnArgument()的结果如下,存在两个结果



4getQualifier()

返回进行方法调用的对象(如果存在的话)。返回的结果为Expr类型。MethodConstructor类可用。

c.getQualifier()结果为



5getEnclosingCallable()

返回包含这个方法调用的最近的可调用实体(例如方法或构造函数),该谓词继承于Expr类。返回的结果为Callable类型。MethodConstructor类可用。

c.getEnclosingCallable()结果为





6、MethodCall 类

作用

MethodCall类代表的是程序中所有的方法调用,该类继承于上面的Call类与Expr

常用谓词

示例代码



上面示例使用如下规则查询



定义MethodCall mc,则mc结果如下



除了继承了父类中的常用谓词,还有如下MethodCall类特有的谓词

1getMethod()

用于返回被调用的方法本身。返回的结果为Method类型

mc.getMethod()结果为



2getArgument(i)

返回方法调用中的第 i 个参数。返回的结果为Expr类型

mc.getArgument(1)结果为



3getType()

获取调用方法的返回结果的类型。返回的结果为Type类型

此处mc.getType()的结果是 String



4getReceiverType()

获取当前方法调用中方法的限定符的类型,如果没有限定符,则获取封闭类型。返回的结果为RefType类型

此处mc.getReceiverType()的结果如下



7、ConstructorCall 类

作用

ConstructorCall类代表 Java 代码中的构造器调用。这通常包括使用new关键字创建对象的实例,以及在类的构造器内部可能出现的对其他构造器的调用(例如,通过this()super()

该类继承于上面的Call类与Expr类,专门用于表示与构造器调用相关的表达式。

常用谓词

示例代码





上面示例使用如下规则查询



定义ConstructorCall coc,则coc结果如下



除了继承了父类中的常用谓词,还有如下MethodCall类特有的谓词

1getConstructedType()

返回由构造器调用创建的对象的类型,用于确定新创建的对象的类型。返回的结果为RefType类型

此处coc.getConstructedType()获取的为创建的File对象



2getConstructor()

返回被调用的构造函数。返回的结果为Constructor类型

此处coc.getConstructor()获取的为java.io.File#File构造函数







常见的特殊情况

在针对项目编写CodeQL规则时,经常碰到数据流中断的情况。下面根据我碰到的部分情况以及其他师傅们碰到的情况来整理一下到底哪些情况会导致污点数据的传播断掉



全版本通用情况

一、第三方包调用

CodeQL官方Github中有个issueshttps://github.com/github/codeql/issues/6729。其中提到,在进行流和污点分析时,CodeQL仅分析通过用户代码的污点路径。对第三方包的调用被视为黑盒,也就是无法预测的,因此CodeQL不会将这个过程连接起来。

image.png


所以CodeQL官方提供了一个可以实现并描述这些数据如何传入和传出的谓词isAdditional{Flow|Taint}Step,用于用户自定义的将不同节点连接起来。

关于第三方包调用的话主要就两种情况

1 污点作为参数传播到第三方包的函数调用的返回结果中,即a = "污点"; b = jar. Func(a)

2 污点作为参数传播到第三方包的类中实例化成一个对象,即a = "危险"; B b = new Jar(a)



下面简单举例说明一下,在复杂场景下也是通用的

(1)传播到第三方包的参与函数调用

如下图,其中CheckPathLegal是我自己定义的Check包中的类,其中的handlePath方法被用来对一个path路径进行处理(PS:此处将../置空的处理方式依旧存在安全问题)

image.png


存在漏洞的readFile9方法中,将filePath传入CheckPathLegal类的handlePath方法中进行处理,并且将处理后的filePath返回,再调用new File()生成File对象后,再将其传入FileReader方法中,这种情况,file对象应该是受污染的

image.png


但是默认情况下,CodeQL进行代码分析时,只会分析用户的代码,对于第三方包的方法调用被将认为是不可预测的。所以CodeQL无法扫描出结果

image.png


可以看到有问题的readFile9方法并没有被扫描出来

由于在CheckPathLegal.handlePath(filePath)处数据流就已经断开了。那么就需要将这两个节点连接起来,这里我们使用isAdditionalFlowStep进行连接

image.png


可以看到,将断开的两个节点连起来后,CodeQL就可以找到存在漏洞的readFile9方法了

isAdditionalFlowStep谓词的内容如下:



(2) 传播到第三方包的类中进行实例化

如下图,其中CreateFile是我自己定义的Check包中的类,用来创建一个File对象以及获取该对象



存在漏洞的readFile7方法中,将filePath传入new CreateFile()中创建createFile对象,再调用createFile对象的getFile方法获取到File对象后,再将其传入FileReader方法中,这种情况,file对象应该是受污染的

image.png


依旧和上面一样,默认情况下,对于第三方包的方法调用下,CodeQL无法扫描出结果

image.png


可以看到有问题的readFile7方法并没有被扫描出来

因此,想让数据流进入FileReader中是不行的,因为在new CreateFile ()getFile()方法就已经断开了。那么就需要将这两个节点连接起来

image.png


可以看到,将断开的两个节点连起来后,CodeQL就可以找到存在漏洞的readFile7方法了

isAdditionalFlowStep谓词的内容如下:

二、fastjson

com.alibaba.fastjson库在国内产品十分常用,用于处理 JSON 数据,它可以便捷地将 Java 对象转换为 JSON 字符串,或者将 JSON 字符串转换为 Java 对象。JSONObject表示一个 JSON 对象,其 fastjson 中的一个类。JSONObject#get方法常用于获取一个ObjectgetString常用于获取一个字符串,还有其他如getIntValuegetDouble等方法

有如下的代码案例

上面代码中其中findPersonById2方法存在SQL注入问题 我们使用codeql规则进行扫描,发现getPersonById4方法可以被检测出,getPersonById5对应的Controller被漏报了

1.png


这是因为数据流在String id = jsonRequest.getString("id"); 的时候断流了,但是String id = (String) jsonRequest.get("id"); 处并没有断。

这是因为 com.alibaba.fastjson.JSONObject 实现了 java.util.Map 接口,JSONObject#get 方法其实实际是调用 java.util.Map#get 方法
2.png


并且Codeql官方为该接口get方法进行了建模。getString等方法特定于 JSONObject方法实现,不来自它实现的任何接口,因此该处会存在断流问题。

因此我们就需要将String id = jsonRequest.getString("id");所在的这两个节点连接起来,使用isAdditionalFlowStep进行连接
3.png


isAdditionalFlowStep谓词的内容如下:

三、HashMap问题

由于Codeql的限制,目前的java库中的DataFlow库在跟踪流时无法很好的区分HashMap键。使用一个键在 map 中存储一个值,然后在使用另一个 key 从 map 中获取值会造成误报问题。

有如下的代码案例



此处不存在SQL注入问题,因为ids参数中每一项都使用的了预编译,order键的值直接使用asc常量,并未使用外部传入的参数

但是使用codeql官方规则java/ql/src/experimental/Security/CWE/CWE-089/MyBatisMapperXmlSqlInjection.ql可以检测出此处存在SQL注入问题,这是一个误报
image.png
根据CodeQL官方Github中的issue。目前官方已经知道该限制,后期会有解决方案

image.png




历史版本存在的情况

一、Lombok问题

开发使用 Lombok 的代码通过注解极大地简化了样板代码。但是由于Lombok的工作原理和底层机制,Lombok的代码会在编译期间会使用注释处理器转换为对应的有效Java代码,老版本的CodeQL分析器会在源代码转为有效Java代码前查找源代码,因此会导致CodeQL无法访问到Lombok自动生成的代码,这可能导致使用了Lombok情况下存在的潜在漏洞也无法被检测出来。

但是该问题在codeql-cli-2.14.4版本及之后已经被解决,新版本支持直接解析使用 Lombok 的项目

image.png


该问题官方给出过解决方案,CodeQL官方Github中有个issueshttps://github.com/github/codeql/issues/4984

其中提到,Lombok 无法与 CodeQL Java 分析器一起使用,但是可以在运行 CodeQL 之前先“delombok”源文件
image.png
除此之外有一些师傅们已经给出了其他比较好的解决方法,加上目前新版Codeql-cli已经支持解析lombok,这里不再过多赘述。

二、配置文件问题

该问题相比上面的lombok问题其实也在更早些的版本,官方就已经出了解决方案 该问题codeql-cli- 2.7.2版本及之后已经默认会将XML等格式文件打包进数据库中并且建立索引

image.png


虽然官方已经解决了这个问题,但是我们依旧可以来看一下Codeql的pre-finalize.cmd文件和环境变量对打包数据库的影响

(1)pre-finalize

pre-finalize文件是codeql在创建数据库过程中,用于将build过程中生成的trap文件导入数据库以及为数据库建立索引时用到的脚本文件,win上为pre-finalize.cmd,linux、macos上为pre-finalize.sh

新版本的CodeQL默认会将XML等文件打包进数据库中,但是也会要求所有的配置文件总大小不超过50mb,如果超过50mb的话,并执行indexbyname中的操作,也就是只默认打包AndroidManifest.xml, pom.xml, struts.xml, web.xml这四个文件

image.png


因此我们可以直接修改indexbyname中的内容如下

image.png


旧版本的CodeQL中也可以做如上修改

image.png




(2)环境变量 LGTM_INDEX_XML_MODE

通过将环境变量 LGTM_INDEX_XML_MODE 设置为all ,Codeql在打包时就会执行indexall的内容,可以直接提取单个文件大小 10MB 以下的所有文档并打包,而不管所有文件总大小如何

image.png


通过下面命令设置对应环境变量

Linux上:

export LGTM_INDEX_XML_MODE='ALL'

Win上:

set LGTM_INDEX_XML_MODE=ALL

打包指定格式的文件

如果我想打包指定格式的文件并且建立索引,我们应怎么实现呢

CodeQL官方的issues中多处提到了设置指定的LGTM_INDEX_FILETYPES环境变量

image.png


image.png


因此,想要在打包时将指定格式的文件也建立索引,就可以设置LGTM_INDEX_FILETYPES,如set LGTM_INDEX_FILETYPES=.json:JSON .yaml:YAML .yml:YAML





0 条评论
某人
表情
可输入 255