关于cc链无法在shiro利用的问题
在加入commons-collections-3.2.1
依赖后,使用ysoserial
的CommonsCollections6
打,会出现反序列化失败的问题。看一下tomcat
中出现的提示。
在当前类加载中不能加载[Lorg.apache.commons.collections.Transformer
类。根据网上的文章,现在分析以下原因。
原因分析
找到最后一条错误,下断点进行分析。
可以看到,这是一个ObjectInputStream
的子类,其重写了resolveClass
方法:
resolveClass
方法的作用是将类的序列化描述符加工成该类的 Class 对象。简单来说,需要通过resolveClass
方法将序列化生成的流中所存在的字符串形式的类名,找到对应的java.lang.Class
对象。而出异常时加载的类名为[Lorg.apache.commons.collections.Transformer;
。这个类名表示org.apache.commons.collections.Transformer
的数组。
原本ObjectInputStream
类中resolve
方法如下。对比可以发现Shiro
使用了重新定义的ClassUtils.forName
方法代替了原本ObjectInputStream
类中的Class.forName
。
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException
{
String name = desc.getName();
try {
return Class.forName(name, false, latestUserDefinedLoader());
} catch (ClassNotFoundException ex) {
Class<?> cl = primClasses.get(name);
if (cl != null) {
return cl;
} else {
throw ex;
}
}
}
跟进ClassUtils
类的forName
方法,发现最终使用loadClass
来加载类,默认使用THREAD_CL_ACCESSOR.loadClass
来进行加载,如果不成功会依次再使用
CLASS_CL_ACCESSOR.loadClass
和SYSTEM_CL_ACCESSOR.loadClass
来进行加载。
看一下这三个字体颜色不一样的分别为什么。
//THREAD_CL_ACCESSOR返回当前线程上下文的ClassLoader,在Tomcat中间件中,返回的当前线程上下文的ClassLoader为ParallelWebappClassLoader。
private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
@Override
protected ClassLoader doGetClassLoader() throws Throwable {
return Thread.currentThread().getContextClassLoader();
}
};
//CLASS_CL_ACCESSOR获取ClassUtils类的Class的加载器,这里也是ParallelWebappClassLoader
private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
@Override
protected ClassLoader doGetClassLoader() throws Throwable {
return ClassUtils.class.getClassLoader();
}
};
//获取系统的加载器
private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
@Override
protected ClassLoader doGetClassLoader() throws Throwable {
return ClassLoader.getSystemClassLoader();
}
};
跟着走,先分析Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
到这里的会发现进不去了,因为clazz = cl.loadClass(fqcn);
的loadClass
是Tomcat
的WebappClassLoaderBase
类了。如果需要进一步跟进Tomcat
的运行情况,需要先加入Tomcat
的依赖。这里是mvn
项目,在pom.xml
添加:
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<!--这里的版本需要和自己使用的tomcat版本对应-->
<version>9.0.53</version>
</dependency>
然后进入到loadClass
。这是WebappClassLoaderBase
中的方法,并且继承于URLClassLoader
。
这个方法很长
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
if (log.isDebugEnabled()) {
log.debug("loadClass(" + name + ", " + resolve + ")");
}
Class<?> clazz = null;
// Log access to stopped class loader
checkStateForClassLoading(name);
// (0) Check our previously loaded local class cache
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled()) {
log.debug(" Returning class from cache");
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
...........
...........
}
throw new ClassNotFoundException(name);
}
大体的流程如下:
1.findLoadedClass0
:检查当前要加载的类是否已经被WebappClassLoader
加载过。
2.findLoadedClass
:从java.lang.ClassLoader
类加载缓存检查当前类是否已经被加载过。
3.javaseLoader.loadClass
:尝试使用ExtClassLoader
类加载器加载类。
4.如果前三步没找到,通过filter()
检查类是否在定义的名单范围内,如果在的话则遵循双亲委派机制,使用Class.forName()
加载类。由于delegate
默认为
false
,并且符合filter()
检查的类比较少,所以可以认为Tomcat
在实现大多数类的加载的时候并不遵循双亲委派机制,也就是一般会跳过这一步。
5.在本地仓库中寻找该类,调用findClass实现。clazz = findClass(name);
6.最终,如果以上步骤都无法找到该类,尝试通过Class.forName()
进行加载,如果找不到则抛出ClassNotFound
异常。
问题主要出第五步的时候,通过调用findClass
实现。内部还会调用findClassInternal(name)
,尝试加载类。
跟进去之后发现了getClassLoaderResource
方法。主要在WEB-INF/classes
和WEB-INF/lib/
中搜索根据寻找类名,如果寻找到则将class
文件字节码内容转成字节流,然后调用ClassLoader的defineClass
将字节流转成class
对象,从而完成类的加载。
用于path
中多了个[,很明显是找不到的,因此反回了null
。
接着到第六步通过,Class.forName()
进行加载,如果找不到则抛出ClassNotFound
异常。
可以看到最终调用了ClassLoader
中的forName0
进行加载。因为对应的path
中不包含WEB-INF/lib/
,因此也无法加载到[Lorg.apache.commons.collections.Transformer
这个类,最终抛出异常。
现在
THREAD_CL_ACCESSOR.loadClass
进行加载失败,则使用
CLASS_CL_ACCESSOR.loadClass
和SYSTEM_CL_ACCESSOR.loadClass
来进行加载。而CLASS_CL_ACCESSOR.loadClass
也是ParallelWebappClassLoader
,所有失败的原因一样。最后看SYSTEM_CL_ACCESSOR.loadClass
进行加载。总体与上面的第六步一样,也会找不到而抛出错误。
可以看出在这中间涉及到大量Tomcat
对类加载的处理逻辑。最后的结论:如果反序列化流中包含非Java
自身的数组,则会出现无法加载类的错误。这就解释了为什么CommonsCollections6
无法利用了,因为其中用到了Transformer
数组。
参考文章
Java安全漫谈 - 15.TemplatesImpl在Shiro中的利用
Comments | NOTHING