loadClass和forName加载数组类

在打shiro的时候,我们发现不能用Transformer数组,而能用byte[]数组,这是为甚?

原文:

https://godownio.github.io/2024/08/06/shiro-fan-xu-lie-hua-shen-ji/

ClassLoader.loadClass和Class.forName

先说结论。URLClassLoader#loadClass不能加载数组类,Class#forName可以加载数组类

Class.forName

Class#forName调用了一个native方法进行加载

这个分析不了,但是结论就是forName0可以加载数组类,且根据ClassLoader遵循双亲委派逻辑

ClassLoader.loadClass

ClassLoader#loadClass实现了双亲委派。

还记得之前讲的双亲委派机制吗,loadClass向父加载器传递,到顶层再逐层调用findClass查找类

一般来说ClassLoader的具体实现类都不会修改这个loadClass,而是改findClass方法,改写具体的搜索类的逻辑。比如最常用的URLClassLoader类,也就是ExtClassLoader和AppClassLoader的父类,就没有重写loadClass,而是重写了findClass:

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
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}

发现在这里将全类名转成了/a/b/c.class形式,然后调用defineClass去加载类文件。那么这里实际也解释了为什么常用的ClassLoader.getSystemClassLoader().loadClass无法加载数组,因为数组类的Class名称又带括号又带分号的,比如[Lorg.apache.commons.collections.Transformer;,肯定对应不上文件。

即URLClassLoader不能加载数组类

shiro 中的loadClass

当时Shiro的报错信息里有[Lorg.apache.commons.collections.Transformer;

[L是一个JVM的标记,说明实际上这是一个数组,即Transformer[]

shiro的漏洞点DefaultSerializar.deserialize,在readObject前,用自定义的ClassResolvingObjectInputStream解析了输入流,而不是用默认的ObjectInputStream

该类resolveClass用自定义的forName查找类,而不是Class.forName

resolveClass 是反序列化中用来查找类的方法。读取序列化流的时候,读到一个字符串形式的类名,需要通过这个方法来找到对应的对象。

调用了这什么静态变量的loadClass

也就是ExceptionIgnoringAccessor.loadClass

先getClassLoader获取类加载器,然后调用该类加载器的loadClass

不同中间件的类加载器不同,Tomcat中是ParallelWebappClassLoader

网上下个Tomcat 8.5.6源码分析一下

ParallelWebappClassLoader继承WebappClassLoaderBase,且未重写loadClass,那就看WebappClassLoaderBase.loadClass

草,这个也没重写,我TM跟跟跟

最后跟到WebappClassLoaderBase,挖草,巴拉一串,可以读注释的英文(很快,别读,我直接抄结论

1、先在tomcat的缓存中查找该类是否已经加载过
2、如果没有,在jdk的缓存里查找该类是否已经加载过
3、如果没有,则使用ExtClassLoader#loadClass加载,ExtClassLoader会走双亲委派。这里是为了在打破双亲委派的同时保留加载系统内置类的功能,绕过AppClassLoader。
4、如果没有加载成功,判断是否设置了delegate也就是遵循双亲委派,默认是false,就会调用WebAppClassLoader#findClass方法进行加载。
5、如果没有加载成功,会用父类加载器调用Class#forName进行加载,也就是用tomcat中的CommonClassLoader。

delegateLoad为false,能进if。

可以看到在这个过程里,实际上是既有Class#forName又有ClassLoader#loadClass的。加载类的地方实际就两个WebAppClassLoader#findClass和用CommonClassLoader调用Class#forName

  • findClass为什么没有加载成功呢?

WebAppClassLoader#findClass调用了findClassInternal,name一开始就经过了binaryNameToPath

换成了和URLClassLoader#findClass类似的改成/a/b/c.class形式,也不能加载数组类

  • CommonClassLoader调用Class#forName为什么没有加载成功呢?

Class.forName可以加载数组类,因为进去就处理成正常的Path再进行的双亲委派类加载

但这里传入的CommonClassLoader是一个URLClassLoader,其中的URL只有tomcat/lib下面的jar包,forName进行类加载会遵循双亲委派,配置的类加载器是AppClassLoader,也就是AppClassLoader对应的URLClassLoader及双亲委派向上走到的ExtClassLoader(也是URLClassLoader,只包括tomcat/lib),bootClassLoader Path对应的类都能加载,其中就包括Java原生类和tomcat/lib下面的jar。

其他的数组类因为URLClassLoader只包括tomcat/lib的原因,比如WEB-INF/classes和WEB-INF/lib都加载不了

shiro这里的loadClass,ClassLoader加载数组因为类名改为了数组形式,在前面几次都查找不到,错失良机。走到了Class.forName,在Tomcat作为中间件的情况下,却因为Tomcat自定义双亲委派的原因,查找路径又没有我们需要的CC依赖

Tomcat双亲委派你个骇人鲸

结论就是,URLClassLoader.loadClass不能加载数组类,Class.forName可以加载数组类。Shiro中自定义了loadClass,只能加载Tomcat lib和JAVA原生数组类

2021 0ctf buggyLoader loadClass

题目来自2021 腾讯0ctf buggyloader。题目环境:

https://github.com/waderwu/My-CTF-Challenges/tree/master/0ctf-2021-final/buggyLoader

gitdown直接下载文件夹

http://tool.mkblog.cn/downgit

题目很明显了,用CC链打

springboot项目,直接看Controller

IndexController自己定义了MyObjectInputStream解析输入流

resolveClass调用loadClass寻找类

ClassLoader在构造函数给出了,URLClassLoader。

URLClassLoader.loadClass不能加载数组类。

一个数组类也不能传,怎么打CC?

用到了我们上一节讲的,JRMP。利用反序列化漏洞开启RMI服务,再向RMI打CC链,形成二次反序列化

上一篇:
JNDI注入(InitialContext.lookup)
下一篇:
RMI三端源码分析&JRMP及其高版本绕过