关于cc链无法在shiro利用的问题

发布于 2021-11-26  435 次阅读


关于cc链无法在shiro利用的问题

在加入commons-collections-3.2.1依赖后,使用ysoserialCommonsCollections6打,会出现反序列化失败的问题。看一下tomcat中出现的提示。

image-20211126192551951

在当前类加载中不能加载[Lorg.apache.commons.collections.Transformer类。根据网上的文章,现在分析以下原因。

原因分析

找到最后一条错误,下断点进行分析。

image-20211126192800157

image-20211126192816020

可以看到,这是一个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.loadClassSYSTEM_CL_ACCESSOR.loadClass来进行加载。

image-20211126194555826

看一下这三个字体颜色不一样的分别为什么。

//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);

image-20211126195328420

到这里的会发现进不去了,因为clazz = cl.loadClass(fqcn);loadClassTomcatWebappClassLoaderBase类了。如果需要进一步跟进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),尝试加载类。

image-20211126210738843

跟进去之后发现了getClassLoaderResource方法。主要在WEB-INF/classesWEB-INF/lib/中搜索根据寻找类名,如果寻找到则将class文件字节码内容转成字节流,然后调用ClassLoader的defineClass将字节流转成class对象,从而完成类的加载。

image-20211126210902270

用于path中多了个[,很明显是找不到的,因此反回了null

接着到第六步通过,Class.forName()进行加载,如果找不到则抛出ClassNotFound异常。

image-20211126211925267

可以看到最终调用了ClassLoader中的forName0进行加载。因为对应的path中不包含WEB-INF/lib/,因此也无法加载到[Lorg.apache.commons.collections.Transformer这个类,最终抛出异常。

image-20211126212443974

image-20211126212456251


现在THREAD_CL_ACCESSOR.loadClass进行加载失败,则使用

CLASS_CL_ACCESSOR.loadClassSYSTEM_CL_ACCESSOR.loadClass来进行加载。而CLASS_CL_ACCESSOR.loadClass也是ParallelWebappClassLoader,所有失败的原因一样。最后看SYSTEM_CL_ACCESSOR.loadClass进行加载。总体与上面的第六步一样,也会找不到而抛出错误。

image-20211126213759521

可以看出在这中间涉及到大量Tomcat对类加载的处理逻辑。最后的结论:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。这就解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组。

参考文章

http://xiashang.xyz/2020/10/03/Shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E7%AC%94%E8%AE%B0%E4%B8%89%EF%BC%88%E8%A7%A3%E7%96%91%E7%AF%87%EF%BC%89/

Java安全漫谈 - 15.TemplatesImpl在Shiro中的利用


沙上有印,光中有影!