CodeQL学习
2024-08-09 18:23:22

本文为个人学习笔记+一些个人的理解调试,仅供学习参考

首先存几个学习地点
https://drun1baby.top/2023/09/03/CodeQL-%E5%85%A5%E9%97%A8/
https://www.freebuf.com/articles/web/283795.html
https://codeql.github.com/docs/codeql-overview/about-codeql/

什么是 CodeQL

这里我本来想用一句话总结的,但是自己学的还不够,所以无法总结,深入学习之后再总结

CodeQL 配置

首先要先下载 CodeQL 的 CIL,这个官网的 releases 上有,我们选择 win64 版本的安装包
然后下 SDK

1
git clone https://github.com/Semmle/ql

下完之后的目录结构如下
image.png然后配置环境变量,配置一个 codeql 的路径即可,这里的路径以我的为例就是

1
H:\CodeQL\codeql

然后运行一下 codeql 命令,出现如下内容就没太大问题了
image.png

CodeQL 使用

0x01 测试 Helloworld

Codeql 处理对象并不是项目源码本身,而是中间生成的 AST 结构数据库,所以每次分析之前,我们需要将项目源码转化成 Codeql 能够识别的 CodeDataBase
这里以我们从micro_service_seclab靶场获取的源码为例,稍微看一下目录结构,作者写的一个很标准的 Springboot 的项目
image.png
然后我们执行如下命令

1
codeql database create ./databases/micro_service_seclab_database  --language="java"  --command="mvn clean install --file pom.xml" --source-root=./practice/micro_service_seclab/

但是初次运行,有很大概率会报错
这个原因有很多,解决方法可参考这位师傅,找了很多关于 java 本身 maven 编译项目的问题,都无法解决
https://blog.csdn.net/m0_63303407/article/details/128750198
然后是我的环境变量
image.png
我个人这么配置之后基本上没啥问题了
然后是 Vscode 中配置一下我们刚才生成的 database,配置完这个之后我们就能够写查询语句了
image.png
在当前这个目录下,新建一个 demo.ql 文件,用来写我们的查询语句,这里我们简单的 select 出来一个 helloworld 字符串
image.png

0x02 CodeQL 基本语法

我们最开始提到了在 CodeQL 中,代码被视作数据,而漏洞,错误或者其他报错都被建模为可以针对的从代码中提权的数据库查询。其实就拿刚才的项目来说,我们先将项目中的内容转化为 Codeql 能够识别出来的数据库,这个过程其实就是 codeql 引擎将我们的项目内容(java 代码)转化为了我们 codeql 可识别的 AST 数据库,然后按照 Codql 中的编写的规则去查询数据库中的数据。这个思想其实在我看来就是将面向对象的数据库查询
稍微理解了一下 Codeql 整体的流程,看个例子

1
2
3
4
5
import java

from int i
where i=1
select i

image.png

  • import java 由于我们审计的是 java 项目,所以要导入 java 的类库
  • from int i 这句代码表示我们从 int 这个整数组中取一个变量作为 i,其实这一步更像是给 i 这个属性定类型,int i
  • where i= 1 就是当我们的 i 为 1 时,就执行下面的逻辑
  • select 1 也就是在上一句的 where 条件达成之后,我们执行一段 select 语句

其实大部分的 codeql 代码都可以浓缩为这一段

0x03 类库

上面提到了 codeql 引擎把 java 代码转化为了 databases,这里其实有一个细节,就是复数 databases,我们知道 java 是一门典型的面向对象的语言,类是必不可少的,而这里的每一个 dababase,就对应一个类。这也是为什么每一个 java 项目转化之后被称为了 databases 了—-类数据集。而类库的具体实现,就是一个一个 AST 结构表示出来的,这里我们演示一段查看某一 database 的 AST 结构
首先将我们的源码添加到工作区,我们待会能够直接从 vscode 中指定到源码
image.png
添加成功之后你就能够看到在 CodeQL 这个大文件夹外面会多一层源码目录,这个时候我们选定 IndexController.java 文件
image.png
再去 codeql 中点击 view AST,就能够看到当前 IndexController.java 类的 AST 结构了
image.png
当然除了我们本来的这些 java 代码的类库,我们还有 codeql 中帮助我们集成的一些类库,比如 Method,MethodAccess,Parameter 等。这里列出来的三个都是很常见的三个类库,具体作用如下:

  1. Method,方法类库,用来存储当前项目的中的所有方法
  2. MethodAccess,调用方法类库,用来存储当前项目中的所有方法调用
  3. Parameter,参数类库,用来存储当前项目中所有的参数

当然,这三个类库都是有对应类的,名字是一样的。既然是类,那么一定有类方法和类属性,我们这里稍微看一下 Method 类的方法使用

1
2
3
4
5
import java

from Method method
where method.hasName("getStudent")
select method.getName(),method.getDeclaringType()

下面是结果
image.png
method.getName 就是获取到我们搜索到的方法名,method.getDeclaringType 就是获取到定义当前方法的类

0x04 谓词使用

谓词的出现,是为了解决 where 条件过长,影响逻辑分析的情况。其实谓词也可以叫做函数,只不过在 codeql 中都是叫谓词,看如下谓词

1
2
3
4
5
6
7
8
9
10
import java

predicate isgetStudent(Method method) {
exists(|method.hasName("getStudent"))

}

from Method method
where isgetStudent(method)
select method.getName(),method.getDeclaringType()

格式和函数是差不多的

predicate 表示当前方法没有返回值
exists 叫做子查询,他可以根据参数中的子查询来返回 true 或者 false,这里要注意一下格式,子查询前面有一个 |,这个格式其实用意在于,| 前半段的数据作为参数,| 后半段的代码作为执行的判断逻辑代码,前面的参数是可以传递给后半段的代码执行中的

0x05 Node,Expr,Param

这一块算是自己单独分出来的一部分笔记,因为我学到后面之后有些参数的意思不能够瞬间反应出来,觉得有些基础概念还是没过
Expr 和 Param 分别叫做表达式和参数,他们两者都是 Node 的类型,并且 Node 就这两种类型,我们能够分别通过 asExpr()和 asParam()方法将他们取出来
Expr:

1
2
3
4
5
6
public List<Student> getStudent(String username) {
//String sql = "select * from students where username like '%" + username.get() + "%'";
String sql = "select * from students where username like '%" + username + "%'";
//String sql = "select * from students where username like ?";
return jdbcTemplate.query(sql, ROW_MAPPER);
}

0x06 source ,sink, sanitizer

sink 应该很熟悉了, 指最终执行点。source 指的是入口点,也就是漏洞污染链条输入点。sanitizer 叫做净化函数,在整个漏洞链条的执行过程中如果出现了一个点,能够阻断整条攻击链的逻辑执行,那么这个点就叫做sanitizer
总结一下,如果用 codeql 编写规则去找漏洞,我们要保证 source 和 sink 都存在,并且链中不存在 sanitizer 即可

这里我们进行一个关于 Springboot 中的 SQL 注入的攻击链寻找,那么最终触发的方法(sink)一般是 query 或者 exeSql 了。当然还需要考虑入口点,也就是 source,Springboot 中一般就是各路由下的接收变量参数了

1x01 设置 source

在 codeql 中我们可以用如下谓词来查询 source

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

由于我们当前分析的是 SpringBoot 的项目,入口点应该是每一个具有接收数据流的参数,以及它对应的类
比如 IndexController 中大部分路由下都有的一个 RequestParam 参数
image.png
这些都是我们需要去收集的 source
那收集这些参数的谓词该怎么写呢?我们可以拿 Codeql 中原生的规则来用

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

这是 SDK 中自带的规则,里面包含了大多数常用的 Source 入口
这段初看还是有点懵的,先看 RemoteFlowSource,它是一个类库,具体位置在 semmle.code.java.dataflow.FlowSources,本来的用意是存储可能的远程用户控制的数据流源,它里面内置了很多常见的 source 入口,包括我们本次要用到的 Springboot 中的常见传参
至于谓词里面的内容,我们之后再记,这里插个 flag

1x02 设置 sink

开头提到了我们本次案例查找 SQL 注入的最终触发方法是 query 方法或者 exeSql 方法,所以在 sink 的谓词中,我们会直接查找 query 或者 exeSql 方法,以及它的调用

1
2
3
4
5
6
7
8
override predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodAccess call |
method.hasName("query")
and
call.getMethod() = method and
sink.asExpr() = call.getArgument(0)
)
}

这里的语法初见肯定会奇怪,我们拆开来分析
谓词结构:

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

里面的内容先不看,我们只看整体的结构,参数 DataFlow::Node 类型的 sink,这里举个例子来理解:java 种各式各样的攻击链,期间一定会有一段参数是一直在传递的,这个参数一般就是我们的最开始输入的 payload。我们再看 source 谓词的参数:DataFlow::Node src,也就是这个 DataFlow::Node 其实就是我们一开始输入的正常参数或者 payload
我们再看谓词里面的内容 :

1
2
3
4
5
6
7
exists(Method method, MethodAccess call |
method.hasName("query")
and
call.getMethod() = method
and
sink.asExpr() = call.getArgument(0)
)

其实整体就是一个 exists 子查询,exists(object| …..),这里接收到了 Method,以及调用参数 MethodAccess。后续的调用中我们会判断该方法名是否等于 “query”。然后判断调用当前方法的方法
其实这里我一直不太理解为什么要加call.getMethod() = method,甚至可以说我不太理解这整段关于 sink 的寻找,直到下面整体使用的时候才稍微清晰一点,这里就拿后面的理解提前分析一下这段关于 sink 的子查询了
首先是 method.hashName("query"),他其实就是先从所有调用流中将我们所有包含 query 方法的数据流给检索出来,当然这里检索出来可能并不会直接调用到 query 方法,可能是调用链的第 N 环
然后是call.getMethod() = method,结合上面我们检索出的数据流,我们查找调用方法中,当前正在调用 query 方法 Node 节点
最后 sink.asExpr() = call.getArgument(0),就是判断 sink 点是否作为当前方法调用的第一个参数,结合上面两个流程,意思就是说我们的 sink 点要作为 query 方法的第一个参数

1x03 Flow 数据流

我们确定好开头 source 以及链尾 sink 之后,还需要保证整条攻击链是打通的,也就是说不能够出现跟进一半流程跟不下去的情况。那么该怎么去写 codeql 代码保证 Flow 数据流能够正常流通呢?这个确保联通的工作其实交由 codeql 引擎帮助我们完成,我们可以通过Vulnconfig.hasFlowPath 方法来判断是否联通
测试代码如下:

1
2
3
from VulnConfig config,DataFlow::PathNode source,DataFlow::PathNode	sink
where config.hasFlowPath(source,sink)
select source.getNode(),source,sink

1x04 整体查询

Codeql 的语法其实和 java 的很像,写完之后才理解为什么说 codeql 是面向对象版的 sql 查询

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
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.QueryInjection
import DataFlow::PathGraph


class VulConfig extends TaintTracking::Configuration {
VulConfig() { this = "SqlInjectionConfig" }

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

override predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodAccess call |

method.hasName("query")
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"

其实 isSource 或者 isSink 如果直接按照我上面分析的一样去写是会报错的,因为加了一个限定词 Override,很明显它肯定是一个重写方法,那他最开始的定义在哪里呢?我们跟进TaintTracking 的定义
发现TaintTracking 很像一个接口形式的类,也就是大致的包含和定义还是规定了,但是具体的定义没有写
image.png
有一个TaintTrackingImpl,我们拿面向对象的思想去套,说不定它里面就有呢?跟进之后发现这是一个文件,后缀是 qll,这个没太了解,但是我么能够在这个文件中找到Configuration extends DataFlow::Configuration的定义,这也是我们为什么定义的 Class 要继承TaintTracking::Configuration,往后翻就能在Configuration 中找到 isSource 和 isSink 的定义
image.png
相信整体的代码也能够理解了,为什么要先写一个类继承自TaintTracking::Configuration,然后再写 isSink 和 isSource 方法,这跟面向对象的语法其实是很像的
再解析一下最后三行的代码:
实例化(?)一个我们重写的 VulnConfig,然后获取两个 DataFlow 的节点 Node,分别叫做 source 和 sink,只不过之后放入 config 中就会由 codeql 引擎去判断以及我们写的 issink 或者 issource 是否为 sink 和 source,然后判断数据流是否通了。

1
2
3
from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"

结果如下
image.png
这里可能还有一个疑问,isSource 和 isSink 是在哪里执行的?跟进hasFlowPath()就能够找到具体执行的点了
image.png

结果优化

0x01 误报

我们一个一个去检查结果,发现在第三个结果中出现了误报现象:
image.png
其实靶场的作者也帮我们标注出来了,发现 source 的传参类型是 List的类型,也就是长整数类型,我们并不能够通过参数的传入打入 SQL 注入
其实大多数情况下,如果结果 ql 代码这么写的话,都是会出现这种情况的误报的,那如何去解决这个问题呢?
这里我们可以用到TaintTracking::Configuration中提供的方法—isSanitier,他是 Configuration 中提供的净化方法,我们能够从源码中找到它的原型定义
image.png
image.png
isSanitier 方法它本身是会对基础类型以及一些奇怪类型进行判断的:

1
2
3
4
5
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or
node.getType() instanceof BoxedType or
node.getType() instanceof NumberType
}

第一个类型好像在 Unity 引擎中见到过,但是第二个类型是真没见过,当然第三个类型就是数字类型了。也就是这三种类型,如果 DataFlow 在当前节点中判断出是这三种基础类型,就会直接切断污染链的运行
我们可以通过重写该方法来实现更多类型的过滤

1
2
3
4
5
6
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or
node.getType() instanceof BoxedType or
node.getType() instanceof NumberType or
exists(ParameterizedType pt| node.getType() = pt and pt.getTypeArgument(0) instanceof NumberType )
}

我们重点关注这句exists(ParameterizedType pt| node.getType() = pt and pt.getTypeArgument(0) instanceof NumberType )
传参的类型是ParameterizedType,他其实是一个关于泛型参数的 class,它所代表的其实是泛型中的参数,比如 List,那么ParameterizedType 就代表 Long
image.png
更加普适的来说,上面的 ql 代码其实是为了实现这么一个功能: List,我们获取到泛型中的参数 E 之后,判断它是否为 NumberType 类型,如果是的话,isSanitizer 会返回 true,也就是当前污染链切断。
这里还进行了一段pt.getTypeArgument(0)的执行,<E,L>泛型中不一定只会有一个参数,ParameterizedType 获取参数时,获取的是泛型里面整体,但是我们本次实验中仅仅只针对这一项误报而做出的处理,所以getTypeArgument(0)就能够定位到 Long
添加上isSanitizer 的重写之后,我们再看结果,发现确实解决刚才的误报问题了
image.png

0x02 漏报

误报的问题能够从结果中自己检索出来,但是如果出现漏报问题就很难受了,我们其实是看不出来的,只能说是事后复盘解决(在有正确思路的情况下)。可以先看看这个漏报的点在哪
IndexController 下的/optinal_like路由
image.png
我们跟进它的getStudentWithOptional 方法
image.png
对比我们 select 出来的结果,发现是没有这条 sink 的,具体流程其实是到了定义 sqlWithOptional 的时候,我们传参进来的是 username,此时会调用 username 的 get 方法,可能是 Codeql 的引擎并没有识别到 Option 类型,所以无法调用 useraname 的 get 方法,于是这条攻击链就断了
下面的解决方案可能不适合所有漏报的情形,因为仅仅只是针对这一个漏报进行的修复
在 codeql 中我们采用 isAdditionalTaintStep 方法来强制将两节点连接,从而使攻击链能够走下去
我们首先看如何定位到当前 node,也就是我们调用 get 方法,以及 username 传进来的这个节点
根据文章中的谓词使用,浅写一个测试代码,帮助我们了解这个isTaintedString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.QueryInjection
import DataFlow::PathGraph


predicate isTaintedString(Expr expSrc, Expr expDest,Method method, MethodAccess call, MethodAccess call1) {
exists(|
expSrc = call1.getArgument(0)
and expDest=call
and call.getMethod() = method
and method.hasName("get")
and method.getDeclaringType().toString() = "Optional<String>"
and call1.getArgument(0).getType().toString() = "Optional<String>"
)
}

from DataFlow::Node a,DataFlow::Node b,Method method,MethodAccess call,MethodAccess call1
where isTaintedString(a.asExpr(), b.asExpr(),method,call,call1)
select a,b,method,call,call1

结果如下
image.png
首先我们要明白当前的isTaintedString谓词是为了判断我们是否定位到了 usernameusername.get这两个点,如果定位成功,isAdditionalTaintStep后续会将这两个节点拼接,当前这条攻击链就能够顺利进行,我们最终检索出的 sql 注入点就不会缺少这一条可能了
expSrc = call1.getArgument(0) expDest=call,expsrc 和 expDest 两者就是username 和username.get,后续的描述条件说白了就是:我们尽可能的将当前的情形描述详细,能够准确定位到当前的情况
image.png

  1. call.getMethod() = methodmethod.hasName("get")两种条件描述都是为了定位到 username.get而服务的,两者交换顺序也是没问题的,就像一条工作流,我们为了得到其中的某种物质,漏斗的顺序放置是对结果没有区别的
  2. call1.getArgument(0).getType().toString() = "Optional<String>"定位到getStudentWithOptional(Optional<String> username)而服务
  3. method.getDeclaringType().toString() = "Optional<String>"定位 get

虽说这么做了之后确实能够定位到这一个漏报的 SQL 注入点,但其实遇到其他情况的漏报就有点不够用了,或许有更好的方法也说不定(?

0x03 Lombok 问题

Lombok 是一个用来帮助写类定义的插件,我们可以通过注解来完成 get set 方法以及构造方法的书写
那么这里就会出现一个问题,如果我们所审计的项目中使用了 Lombok,codeql 是无法识别出这些类的 setget 方法的
在 Codeql 的官方 issue 中是有提到这个问题的,解决方案如下

1
2
3
4
5
6
7
8
9
10
11
12
13
# get a copy of lombok.jar
wget https://projectlombok.org/downloads/lombok.jar -O "lombok.jar"
# run "delombok" on the source files and write the generated files to a folder named "delombok"
java -jar "lombok.jar" delombok -n --onlyChanged . -d "delombok"
# remove "generated by" comments
find "delombok" -name '*.java' -exec sed '/Generated by delombok/d' -i '{}' ';'
# remove any left-over import statements
find "delombok" -name '*.java' -exec sed '/import lombok/d' -i '{}' ';'
# copy delombok'd files over the original ones
cp -r "delombok/." "./"
# remove the "delombok" folder
rm -rf "delombok"

大概的操作就是通过去掉代码里面的 lombok 注释,并且还原 setter 和 getter 方法,这样就和正常的类效果是一样的了

所以最终我们关于这个项目的 SQL 注入查找的 codeql 代码写为:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.QueryInjection
import DataFlow::PathGraph


predicate isTaintedString(Expr expSrc, Expr expDest) {
exists(Method method, MethodAccess call, MethodAccess call1 |
expSrc = call1.getArgument(0)
and expDest=call
and call.getMethod() = method
and method.hasName("get")
and method.getDeclaringType().toString() = "Optional<String>"
and call1.getArgument(0).getType().toString() = "Optional<String>"
)
}

class VulConfig extends TaintTracking::Configuration {
VulConfig() { this = "SqlInjectionConfig" }

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

override predicate isSink(DataFlow::Node sink) {
exists(Method method, MethodAccess call |

method.hasName("query")
and
call.getMethod() = method
and
sink.asExpr() = call.getArgument(0)
)
}
override predicate isSanitizer(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or
node.getType() instanceof BoxedType or
node.getType() instanceof NumberType or
exists(ParameterizedType pt| node.getType() = pt and pt.getTypeArgument(0) instanceof NumberType )
}

override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
isTaintedString(node1.asExpr(), node2.asExpr())
}
}

from VulConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select source.getNode(), source, sink, "source"

持续工程化

经过分析测试过后,我们得到了一份关于常规 Springboot 项目的能够进行 SQL 注入检测的 codeql 代码,现在要考虑如何才能将这份代码用于实践
两行命令,按照我们之前所有的步骤来总结就两步:

  1. 生成中间数据库

    1
    codeql database create ~/CodeQL/databases/micro-service-seclab  --language="java"  --command="mvn clean install --file pom.xml -Dmaven.test.skip=true" --source-root="~/Code/micro-service-seclab/"
  2. 运行 codeql 代码,这里是执行刚才写好的 codeql 代码,然后将结果输出到 csv 文件

    1
    codeql database analyze /CodeQL/databases/micro-service-seclab /CodeQL/ql/java/ql/examples/demo --format=csv --output=/CodeQL/Result/micro-service-seclab.csv --rerun

Codeql 语法查漏补缺

其实刚才一路跟过来,我为了弄懂一些语法而写了很多测试代码,期间也跟进过源码进行一些方法的查看,给我的感受就是大部分的语法我都很陌生,所以这里单独开一个用来记录的篇章,主要是为了记录一些初见不是很能理解的语法

Instanceof

java 里面其实也见到过,只不过在 Codeql 的源码中我们能够经常看到,并且在指定扫描 source 时用的特别多
就拿我们本次的 isSource 判定来说

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

我们并没有写很多的 exist 子查询来判定 source,只是通过判定节点 src 是否 instance RemoteFlowSource 就完成了 source 的检索工作,可以想象RemoteFlowSource 里面应该集成了很多类型的 source 用作判定
image.png
可以看到 ReomteSource 的定义中有许多的子类扩展,这里和 java 的语法还有点不太一样,ReomteSource 是一个 abstract 的抽象类定义,而 java 中的抽象类必须要有实现类和对应的实现方法才能够使用功能,这里我们使用ReomteSource 时没有具体的指定要实现它的某些方法,但是我们依然能够用到它的功能。原因就在于 Codeql 中的抽象类,只要我们选择继承,并且在某处有调用的话,所有的子类都会被调用一遍,也就是说,这里所有的 RemoteSource 的子类都会在我们 src instanceof RemoteSource 的时候被调用,那其实大部分的常见入口点 source 都会被检测一遍了

子类递归问题

面向对象的一个常见问题,当我们的一个类中出现了子类的定义时,需要一些特殊的处理才能够准确区分和识别到子类与父类
比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class StudentService {

class innerOne {
public innerOne(){}

class innerTwo {
public innerTwo(){}

public String Nihao() {
return "Nihao";
}
}
public String Hi(){
return "hello";
}
}

}

这里我们定义了三个类:StudentService,innerOne,innerTwo
如何通过 innerTwo 定位到最外层的 StudentService 呢?

1
2
3
from Class classes
where classes.getName().toString() = "innerTwo"
select classes.getEnclosingType().getEnclosingType()

这里我们先从所有的 class 定义中获取到 innerTwo 的定义,然后通过getEnclosingType()来获取其上一级的封闭类定义,跟 java 中的getEnclosingType()简直不要太一样
实际情况下,我们肯定不知道目标类到底有多少层封闭类的定义,所以这么做只存在于我们知道目标类具体结构的情况下才能使用的
还有一种方法能够自动递归来获取其封闭类

1
2
3
4
5
6
7
8
9
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.QueryInjection
import DataFlow::PathGraph


from Class classes
where classes.getName().toString() = "innerTwo"
select classes.getEnclosingType+()

image.png
或者我们可以将+改写为*,都是可以的
image.png

类型筛选

如何筛选出我们想要的类型参数也是一个大问题,通常我们首先会通过如下的代码获取到当前项目中所有的调用参数

1
2
from Parameter param
select param, param.getType()

image.png
然后我们可以用(RefType)来进行过滤

1
2
from Parameter param
select param, param.getType().(RefType)

这里的 RefType 代表的是什么呢?它相当于一层 filter,将所有的 6种数字类型(byte/short/int/long/float/double)、1种字符型(char)、1种布尔型(boolean) 过滤了一遍,再将结果输出出来
我们还可以指定过滤一些特定的类型,这里就是指定筛选出所有的 int 类型

1
2
from Parameter param
select param, param.getType().(IntegralType)

image.png